run program with administrator access to write to registery - delphi

i tried to use this tutorial to work:
https://stackoverflow.com/a/14710803/1149172
first create a file with name uac.manifest with provided content
then create uac.rc width content of 1 24 "uac.manifest"
then copied the files to program folder (in delphi program sources folder) and changed the project file like this:
program Project4;
{.$R 'uac.res' 'uac.rc'} // UAC only
uses
Vcl.Forms,
Unit6 in 'Unit6.pas' {Form6};
{$R *.res}
begin
Application.Initialize;
Application.MainFormOnTaskbar := True;
Application.CreateForm(TForm6, Form6);
Application.Run;
end.
at last i put my registery code at the form
procedure AddEntryToRegistry;
var key: string;
Reg: TRegIniFile;
begin
key := 'Software\Microsoft\Windows\CurrentVersion\Run';
Reg := TRegIniFile.Create;
try
Reg.RootKey:=HKEY_LOCAL_MACHINE;
Reg.CreateKey(Key);
if Reg.OpenKey(Key,False) then Reg.WriteString(key, 'MyApp', 'c:MyApp.exe');
finally
Reg.Free;
end;
end;
every thing seems ok and i dont got any runtime rror
but after clicking the button nothing happened (dont shoe any error and dont add the key to registery)!!!
where is wrong with my works!?
my delphi is xe5 and working on win 8ul

Firstly, I'm going to take it as read that your program is running elevated. If that's not happening then there's no need to look at any code. You did not say otherwise, so let us proceed under the assumption that you are succeeding to elevate.
You are suffering from the registry redirector. Your 32 bit process is running on a 64 bit machine. And so HKLM\Software is redirected to the 32 bit view, stored at HKLM\Software\Wow6432Node.
You can, if you need, use the KEY_WOW64_64KEY flag to access the 64 bit view. Combine this with the flags in the registry object's Access property.
However, the system reads keys from both 32 and 64 bit views of the registry when enumerating the startup programs so you do not need to do this. For the sake of simplicity and predictability I would leave your 32 bit program writing to the 32 bit view.
Your call to CreateKey should be removed. The system creates that key and you can safely assume it exists. And you should not use TRegIniFile. Use TRegistry instead.
Your code should look like this:
procedure AddEntryToRegistry;
var
Reg: TRegistry;
begin
Reg := TRegistry.Create(KEY_ALL_ACCESS);
try
Reg.RootKey := HKEY_LOCAL_MACHINE;
if Reg.OpenKey('Software\Microsoft\Windows\CurrentVersion\Run', False) then
Reg.WriteString('MyApp', 'C:\MyApp.exe');
finally
Reg.Free;
end;
end;
Should you feel that you need to write to the 64 bit view then it is done like this:
procedure AddEntryToRegistry;
var
Reg: TRegistry;
begin
Reg := TRegistry.Create;
try
Reg.RootKey := HKEY_LOCAL_MACHINE;
Reg.Access := KEY_ALL_ACCESS or KEY_WOW64_64KEY;
if Reg.OpenKey('Software\Microsoft\Windows\CurrentVersion\Run', False) then
Reg.WriteString('MyApp', 'C:\MyApp.exe');
finally
Reg.Free;
end;
end;
Judging by your comments, it looks like you might be failing to elevate. The lack of a UAC dialog when your program starts is the tell-tale sign that this is happening. Once your program starts without a UAC dialog, there's no point continuing. You will not write to HKLM without elevation.
Regarding your manifest, you can link only one. So if you want to specify a manifest other than the Enable runtime themes manifest that the IDE can provide, you need to do it all yourself.
In the project options specify that you want to use a custom manifest. That's under Project | Options | Application | Runtime themes. Set the drop down to Use custom manifest. And then supply the file name of your manifest. You'll want to add in the comctl32 v6 part to make sure that you get runtime themes. But don't worry about that now. Just concentrate on getting elevation sorted, and the registry code working.
You are also silently ignoring any errors which does make things a little harder to debug. If it so happens that you are not elevating, then running the code is rather pointless. You know it must fail. But you could at least make it easier to diagnose the problem by throwing an error if OpenKey fails.
procedure AddEntryToRegistry;
var
Reg: TRegistry;
begin
Reg := TRegistry.Create(KEY_ALL_ACCESS);
try
Reg.RootKey := HKEY_LOCAL_MACHINE;
if not Reg.OpenKey('Software\Microsoft\Windows\CurrentVersion\Run', False) then
raise EMyExceptionClass.Create('Could not open registry key');
Reg.WriteString('MyApp', 'C:\MyApp.exe');
finally
Reg.Free;
end;
end;
One final point to make is that writing to this registry key is an admin task. You should require elevation once only, not every time your application starts. If you are planning to require elevation for your application just for this purpose, then you must re-design. This admin task should be performed outside your main application. The most natural place is inside your install program which users will accept requiring elevation.

You are saving your app path to the Run key of the HKEY_LOCAL_MACHINE hive. You should be using the HKEY_CURRENT_USER hive instead, then you will not need to use UAC elevation anymore (unless your app is doing other things that require admin rights).

Related

please, how to code start-up behaviour in Delphi6?

I have an application, and I put a shortcut to it inside win+startup folder, and all is ok.
Now I wish to change this approach, by coding it, and so I have used the code listed at the bottom of this post.
The code inputs a key inside HKLM, but there is a windows error when system starts:
Access violation at address 004815EB in module 'ap1.exe'. Read of address 00000000.
This error is similar on 3 different computers, running win xp or win 7.
procedure SetAutoStart(AppName, AppTitle: string; bRegister: Boolean);
const RegKey = '\Software\Microsoft\Windows\CurrentVersion\Run'; // Run or
RunOnce
var Registry: TRegistry;
begin
Registry := TRegistry.Create;
try Registry.RootKey := HKEY_LOCAL_MACHINE;
if Registry.OpenKey(RegKey, False)
then begin
if bRegister = False then Registry.DeleteValue(AppTitle)
else Registry.WriteString(AppTitle,
AppName);
end;
finally Registry.Free;
end;
end;
The error is raised by the program that is executed on startup. It has nothing at all to do with the code in the question. You can verify that the code in the question behaves as expected by checking the registry entries using the Registry Editor.
You will need to debug the program that is being executed at startup. You won't be able to attach an interactive debugger. Instead you will need to use trace debugging.

Can't read key from registry

I heared that Windows creates a unique key for a PC which is called "MachineID". I found two locations in my registry. Only the location "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Cryptography" should be correct. I try to read the value by this function:
Function GetMaschineID:string;
var
Reg : TRegistry;
//HKEY_CURRENT_USER\Software\Microsoft\MSNMessenger = {dd239a44-fa0d-43ff-a51c-5561d3e39de3}
//HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Cryptography = a06b0ee0-b639-4f55-9972-146776bcd5e4
begin
Reg := TRegistry.Create(KEY_READ);
try
Reg.Rootkey:=HKEY_LOCAL_MACHINE; //Hauptschlüssel
//Reg.RootKey:=HKEY_CURRENT_USER;
if Reg.OpenKey('SOFTWARE\Microsoft\Cryptography\',false) then //Unterschlüssel öffnen
//if Reg.OpenKey('Software\Microsoft\MSNMessenger\',false) then //Unterschlüssel öffnen
begin
Result:=Reg.ReadString('MachineGuid');
end;
finally
Reg.Free;
end;
end;
This version results in an empty string; you see as comment the result from the registry. The second version for "hkey_Current_user" brings the expected string result.
What is wrong with my code or are parts of the registry read protected?
Possible explanation 1
For HKLM you are subject to registry redirection. You have a 32 bit process and are trying to read a key from the 64 bit view of the registry. By default, your 32 bit process is redirected to the 32 bit view, which (implementation detail) lives under Wow6432Node.
Use the KEY_WOW64_64KEY access flag to read from the 64 bit view. As detailed here: How can I read 64-bit registry key from a 32-bit process?
Possible explanation 2
Your call to OpenKey fails for keys under HKLM because you are requesting write access and standard user does not have write access to HKLM. Use OpenKeyReadOnly instead.
Other advice
At the very least you should have debugged this a bit more. Does the call to Reg.OpenKey succeed or fail? You should have debugged enough to know that. Perhaps you did but did not say. If Reg.OpenKey failed then explanation 2 is most likely. Even then, you may subsequently suffer from the other problem.
Note also that your function does not assign to the function result variable, or raise an error, if the call to Reg.OpenKey fails. I would expect that the compiler would have warned you about that.
procedure TForm1.Button1Click(Sender: TObject);
var
registry: TRegistry;
begin
Registry := TRegistry.Create(KEY_READ OR KEY_WOW64_64KEY);
try
registry.RootKey := HKEY_LOCAL_MACHINE;
if (registry.KeyExists('\SOFTWARE\Microsoft\Microsoft SQL Server\Instance Names\SQL')) and
(registry.OpenKeyReadOnly('\SOFTWARE\Microsoft\Microsoft SQL Server\Instance Names\SQL')) then
begin
showmessage(registry.ReadString('SQLEXPRESS'));
registry.CloseKey;
end
else showmessage('failed');
finally
registry.Free;
end;
end;

TRegistry - why are some keys readible and others not?

I have written the following code:
var
MainForm: TMainForm;
const
SRootKey = HKEY_LOCAL_MACHINE;
SKey = 'SOFTWARE\Microsoft\Windows NT\CurrentVersion\NetworkList\Profiles';
implementation
{$R *.dfm}
{ TMainForm }
procedure TMainForm.GetKeys(OutList: TStrings);
var
Reg: TRegistry;
begin
OutList.BeginUpdate;
try
OutList.Clear;
Reg := TRegistry.Create(KEY_READ);
try
Reg.RootKey := SRootKey;
if (Reg.OpenKeyReadOnly(SKey)) and (Reg.HasSubKeys) then
begin
Reg.GetKeyNames(OutList);
Reg.CloseKey;
end;
finally
Reg.Free;
end;
finally
OutList.EndUpdate;
end;
end;
procedure TMainForm.btnScanClick(Sender: TObject);
begin
GetKeys(ListBox1.Items);
end;
procedure TMainForm.FormCreate(Sender: TObject);
begin
GetKeys(ListBox1.Items);
end;
This does not seem to do anything.
I can verify the registry path (Windows 8.1), I have even changed the SKey for testing and no issues, but certain keys like this one return nothing.
I even tried running the program from Windows as Administrator and still nothing.
Is there something else I need to change? What would make certain keys readible and others not?
Your process is 32 bit, and you are running it on a 64 bit machine. As such you are subject to registry redirection.
The registry redirector isolates 32-bit and 64-bit applications by providing separate logical views of certain portions of the registry on WOW64. The registry redirector intercepts 32-bit and 64-bit registry calls to their respective logical registry views and maps them to the corresponding physical registry location. The redirection process is transparent to the application. Therefore, a 32-bit application can access registry data as if it were running on 32-bit Windows even if the data is stored in a different location on 64-bit Windows.
The key you are looking at
HKLM\SOFTWARE
is redirected. From your 32 bit process, attempts to open this key are redirected to the 32 bit view of the registry, which as an implementation detail, is stored at
HKLM\SOFTWARE\Wow6432Node
What you are trying to do here is to access the 64 bit view of the registry. To do so you need to access an alternate registry view. That means passing the KEY_WOW64_64KEY key when opening any keys.
In Delphi you can achieve that by including KEY_WOW64_64KEY in the Access flags, or by including it in the flags you pass to the constructor.
Reg := TRegistry.Create(KEY_READ or KEY_WOW64_64KEY);
On top of that, for this particular key, due to the registry security configuration for this key, you need to be running with admin rights in order to open the key. Even if you only intend to read it.

How to change file association programmatically without require elevation

How to change file association programmatically when the user does not have admin/elevated rights (Win XP, Vista, 7)? Any ideas about how to work around this? Basically I would like to keep my application as lite as it is now (it doesn't need elevated rights to install and run).
At the moment I offer a GUI interface where the user can change the file association, but if the user has limited rights all it does is to show a message that it cannot do that and it explains to it how to activate the "Run this program as administrator" box then restart the program. If the user has the rights, then I just change the association.
There is a better way to do it and stay 'lite'?
In Windows (since windows 2000) you're allowed to have system-wide file association, which require elevated privileges to be set, and per user file associations.
If you want to stay lite, make a per_user file association and that's it.
Take a look on this article: Changes in File Types and File Association Features in Windows 2000 and Windows Server 2003.
You can use the ShellExecute to spawn your external utility. Make sure to include the Shield icon on your action to indicate it will require elevated permissions. It will then prompt the user and let them know it requires special permissions.
One thing you could do is add flags to your own application that indicate it will be changing permissions. And then run your application again, with the special flags.
For example if your application is
MyApplication.exe
you can spawn
MyApplication.exe /setfiles
which would only set the file associations then exit. That way you only have to ship one executable.
function RunAsAdmin(hWnd: HWND; filename: string; Parameters: string): Boolean;
var
sei: TShellExecuteInfo;
begin
ZeroMemory(#sei, SizeOf(sei));
sei.cbSize := SizeOf(TShellExecuteInfo);
sei.Wnd := hwnd;
sei.fMask := SEE_MASK_FLAG_DDEWAIT or SEE_MASK_FLAG_NO_UI;
sei.lpVerb := PChar('runas');
sei.lpFile := PChar(Filename); // PAnsiChar;
if parameters <> '' then
sei.lpParameters := PChar(parameters); // PAnsiChar;
sei.nShow := SW_SHOWNORMAL; //Integer;
Result := ShellExecuteEx(#sei);
end;
My solution (waiting for better alternatives):
It looks like only the admin can change the association globally. In this light, the best way I can imagine now (but not even by far perfect) is to create a small external utility that implicitly runs with elevated rights. This tool will then change the association. Of course the users without elevated rights will still be unable to change the association.
You can find a solution at this place using the registry (OS is Windows XP) - so it may not be applicable to your request : http://volvox.wordpress.com/2006/06/02/extensions-101/ - Sorry it's in french ... Complete sources (well documented) and executable to download.

Delphi ini file vista/xp/win7

Update: I've added the following code:
function TSettingsForm.AppDataPath: string;
//CSIDL_APPDATA Individual user Data
//CSIDL_COMMON_APPDATA Common to Computer Data
// works so long as people have at least IE 4. (and Win95 or better)
var
r: Bool;
path: array[0..Max_Path] of Char;
begin
r := ShGetSpecialFolderPath(0, path, CSIDL_APPDATA, False) ;
if r then result := path
else result := '';
end;
And I've changed the setinifilename function (See below). It will not create the folder structure.
--End update--
I'm behind the times, on what to and not to do. This is how I am currently saving the settings for my software. I just tested it on Vista not logged in as an administrator, and it gives me an error message cannot write ini file. So I'm guessing I'm supposed to write the data to a data folder? I've never used vista/win7 before, and want this software to be windows 2K+ compatible. What should I do to save the settings. I also really didn't want to mess with the registry, because every little bit you add to it, slows down the computer just that much more... (or so It seems)
Thanks for any input.
procedure TSettingsForm.setinifilename;
var filename:string;
Path:string;
begin
filename:='key.ini';
path:=AppDataPath+'\MyCompanyName\ProductName\';
if NOT DirectoryExists(path) then
CreateDir(path);
inifilename:= path+filename;
end;
procedure TSettingsForm.SaveSettings;
var
appINI: TIniFile;
begin
appINI := TIniFile.Create(inifilename) ;
try
low:= Trunc (edt_low.value);
high:=Trunc (edt_high.value);
appINI.WriteInteger('SPEED','LOW',low);
appINI.WriteInteger('SPEED','HIGH',high);
appINI.WriteString('PROXY','SERVER',edtProxyServer.Text);
appINI.WriteString('PROXY','PORT',edtProxyPort.Text);
appINI.WriteString('PROXY','USERNAME',edtProxyUserName.Text);
appINI.WriteString('PROXY','PASSWORD',edtProxyPass.Text);
// status.text:='Saved Data';
finally
appIni.Free;
end;
end;
procedure TSettingsForm.GetSettings;
Var
appINI : TIniFile;
begin
appINI := TIniFile.Create(inifilename) ;
try
//if no last user return an empty string
edt_low.value:= appINI.ReadInteger('SPEED','LOW',0);
edt_high.value:= appINI.ReadInteger('SPEED','HIGH',0);
low:= Trunc (edt_low.Value);
high := Trunc (edt_high.Value);
edtProxyServer.Text:=appINI.ReadString('PROXY','SERVER','');
edtProxyPort.Text:=appINI.ReadString('PROXY','PORT','0');
edtProxyUserName.Text:=appINI.ReadString('PROXY','USERNAME','');
edtProxyPass.Text:= appINI.ReadString('PROXY','PASSWORD','');
finally
appINI.Free;
end;
end;
In Vista, your program is NOT allowed to write to the program files directory where your program is located.
You now have to save your ini files in the AppData directory.
A description of how to do this in delphi is at:
http://www.theabsolute.net/sware/delphivista.html#datafolder
And to be Vista/Windows 7 compatible, the rest of that web page will be a good guideline.
For your update, you cannot CreateDir more than 1 level deep at once. Use the ForceDirectories function instead:
path:=AppDataPath+'\MyCompanyName\ProductName\';
if NOT DirectoryExists(path) then
ForceDirectories(path);
p.s. Don't be afraid to write program settings to the Registry. That's what the registry is for. In fact, it properly handles settings for different users for you when different users are logged in. The Registry works in the same way in 98/Vista/7. Whereas ini files have actually been depreciated, and are no longer used by Windows.
You say you don't want to mess with the registry because "every little bit you add to it, slows down the computer just that much more". Actually that is NOT true. The registry is simply a database. And if it is 10 MB or 100 MB, the difference in time it takes to access is imperceptable.
It's all those companies selling Registry Cleaner programs that are trying to keep this fairy tale going. Using their cleaners can do you more harm than good. All they need to do is wipe out one or two important entries and you can be in deep doo-doo. Please read this article about Registry Cleaners, and especially the "Marginal performance benefit" section which explains correctly that the problems Windows 98 and earlier had with the Registry have been mostly fixed.
If your program adds more than 2 or 3 KB to the Registry, that will be a lot, and it is an insignificant amount. Use the registry. Do it right.
You should use the ApplicationData directory for your app data, In Delphi you can find this folder programatically using the shell api function SHGetSpecialFolderLocation
Embarcadero have a FAQ page on this, here.
As already mentioned - dont save anything in the app folder.
You should split your configuration settings into two parts :
One part containing the settings that must work regardlees of the user - that part should be stored in COMMON_APPDATA.
A Second part containing the individual users settings (users personal choice of font etc) - that part should be stored in APPDATA
As for the CreateDir, it is true that you cannot create more than one level at a time - however, Delphi has the ForceDirectories function that can do exactly that.
e.g. ForceDirectories('C:\MyFolder\SubFolder\SubSubFolder');

Resources