4

I am looking to find a way to iterate through the users in the Registry i.e. the HKEY_USERS branch of the Registry) in order to remove an entry that might have been created by the application in the user's profile under HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Run when the software is uninstalled (i.e. in the uninstaller). My understanding is that each user has a unique SID e.g. HKEY_USERS\S-1-5-21-1832913631-259515069-2567909844-16342 in the Registry. The objective therefore is to return a list of users SIDs in an array and then loop through removing the String value under Run.

I can use RegGetSubkeyNames to return the list of subkeys under HKEY_USERS:

procedure RemoveAppRunRegEntries();
var
  Subkeys: TArrayOfString;
  I: Integer;
begin
  RegGetSubkeyNames(HKU, '', Subkeys);
  for I := 0 to GetArrayLength(Subkeys) - 1 do
    begin
      RegDeleteValue(HKU, Subkeys[I] + '\Software\Microsoft\Windows\CurrentVersion\Run',
        'App Run String Value');
    end;
end;

However, there are four default entries .DEFAULT, S-1-5-18, S-1-5-19 and S-1-5-20, that I believe are always the same on every Windows installation (they are all on the installs I have checked under Windows 7 and 10), plus additional identical SIDs with _Classes appended to the the end, so for the example SID above, there is also a HKEY_USERS\S-1-5-21-1832913631-259515069-2567909844-16342_Classes subkey. Therefore, before looping through, I need to find a way of removing these entries from the array, so that I only have a list of the SIDs.

Is this the best approach to be taking and how might I remove the entries from the array to leave only the unique user SIDs? Is there anything else I have not thought of?

Martin Prikryl
  • 188,800
  • 56
  • 490
  • 992
Robert Wigley
  • 1,917
  • 22
  • 42

2 Answers2

4

Yes, there is something you have not thought of.

Windows does not necessarily load all the users into HKEY_USERS -- in fact in many cases only DEFAULT and the current user (and perhaps also an admin, if that's a different user) will be visible there. This is because only the users that have actually logged in will have their registry hives loaded.

There can be additional problems with messing with settings of other users if roaming profiles are in use. For these reasons and more, the general recommendation is to just leave them alone. (And Windows will silently ignore a Run entry that points at a program that no longer exists, for similar reasons.)

Best practice is to not touch any user settings at all during the install (and especially not settings for other users). Wait until any given user runs your app (manually) for the first time, and then give them a "welcome experience" that asks whether they want to run the app at startup and other such preferences. So it breaks down like this:

Installer

  • writes HKLM and common files
  • ignores HKCU and user files

Application

  • reads and writes HKCU and user files
  • reads HKLM and common files as defaults only if user versions were not found
Miral
  • 12,637
  • 4
  • 53
  • 93
1

You do not have to remove the keys from the array, just skip them in the loop.

To select the correct keys, I'm checking if there are 7 dashes and no underscore in the key name:

procedure RemoveAppRunRegEntries();
var
  Subkeys: TArrayOfString;
  Subkey: string;
  I, J, Dashes: Integer;
begin
  RegGetSubkeyNames(HKU, '', Subkeys);

  for I := 0 to GetArrayLength(Subkeys) - 1 do
  begin
    Subkey := Subkeys[I];
    Dashes := 0;

    for J := 1 to Length(Subkey) do
    begin
      if Subkey[J] = '-' then
      begin
        Inc(Dashes);
      end;

      if Subkey[J] = '_' then
      begin
        Dashes := -1;
        Break;
      end;
    end;

    if Dashes = 7 then
    begin
      RegDeleteValue(
        HKU, Subkey + '\Software\Microsoft\Windows\CurrentVersion\Run',
        'App Run String Value'); 
    end;
  end;
end;
Martin Prikryl
  • 188,800
  • 56
  • 490
  • 992
  • Brilliant! I hadn't thought of checking the format of the key in this way. Exactly what I was looking for. Thanks Martin. – Robert Wigley Sep 04 '16 at 11:15
  • 1
    A related link, listing all the possible default SIDs, that may be of some use: https://support.microsoft.com/en-gb/kb/243330 Therefore, `S-1-5-18` is the LOCAL SYSTEM account, `S-1-5-19` is the LOCAL SERVICE account and `S-1-5-20` is the NETWORK SERVICE account. The `_Classes` keys link to the user's Classes sections and are not included in the main key apparently. This is why there are two for each user. – Robert Wigley Sep 04 '16 at 11:44
  • This is awesome! I'm doing something kinda similar, but this pointed me in the right direction. 10+ – BoundForGlory Apr 21 '21 at 14:06