Inno Setup 5:如何在[代码]部分手动调用管理安装模式?

问题描述 投票:0回答:1

情况:
大家好,我使用 Inno Setup 5.5.6(u) 来创建我的设置。 它必须是 5.x 版本才能支持某些较旧的操作系统。 默认情况下,UAC 请求提升,如果单击“否”,它会以不提升的方式运行 - 我使用 Martin Prikryl 的第二个 解决方案来让它工作。 我之所以没有将其放在他的评论中,是因为这个主题对我来说似乎有点不同,而且对我来说它看起来更像是 Inno Setup 的普遍问题。

问题:
当没有管理员组成员资格的帐户运行代码并使用来自任何管理员帐户的凭据的“可选”UAC 时,会出现以下问题:Inno Setup 为您的管理员帐户保留“非管理安装模式”(例如卸载条目、桌面图标)。仅输入凭据。由于 IsAdminLoggedOn,程序本身将很好地安装在 ProgramFiles 下。

任何其他用户(包括执行安装的受限用户帐户)都无法调用桌面图标或卸载。

到目前为止我尝试过的:

那么,有人可以帮助我从 [Code] 中手动切换到管理安装模式吗?请不要提出以下建议,因为我已经尝试过但没有成功:

不起作用:
    PrivilegesRequiredOverridesAllowed
  • - 出于兼容性原因,我必须使用 5.x 版本,而那时这个命令根本无法解释。
    不起作用:
  • PrivilegesRequired=admin
  • PrivilegesRequired=none
    - 如果我使用它们,我将无法捕获用户拒绝的高度并提供未升高的安装。
    不起作用:命令行参数
  • /ALLUSERS
  • 理论上应该可以解决问题,但对我来说也不起作用。也许版本5也不支持...
    不起作用:
  • Function IsAdminInstallMode
  • 安装程序也无法识别(当编译错误消息告诉我“未知标识符[...]”时)。
    一种肮脏的解决方案:创建两个额外的从属安装程序,其中一个需要管理员权限,另一个没有,解释如下:
  • https://copyprogramming.com/howto/inno-setup-access-unprivileged-account-folders-from-installer-这需要特权
  • 。但出于其他原因,我已经将一个设置嵌入到一个设置中。我宁愿不再嵌套它们。 理论上,考虑到 Inno Setup 的工作方式以及 UAC 被接受后
  • IsAdminLoggedOn
  • 返回 True 的事实,这应该是可能的(请看这里:
    https://stackoverflow.com/a/65990073/22271111
    不起作用:预处理器解决方案目前也没有选择。我尝试设置
  • PrivilegesRequired={#PrivilegesRequired}
  • 并将 ISPP 的查询添加到
    #define PrivilegesRequired "{code:VerifyAdmin}"
    ,具体取决于代码中的
    IsAdminLoggedOn
    是否为真。但后来我发现这个 [Setup] 设置是在安装程序中静态构建的,运行时的更改不会被识别(至少不会识别代码部分中的更改)。
    Inno 设置指南本身和谷歌结果没有解决这个问题(包括 Stackoverflow),这就是我在这里创建一个问题的原因。
预期解决方案:

如果一切顺利,如果运行时接受 UAC,Inno Setup 应该切换到管理安装模式(伪命令:set {#SetupSetting("PrivilegesRequired")} := 'admin')。如果我可以使用一些 [Code] 部分命令告诉编译器这样做,我会很高兴。

inno-setup uac
1个回答
0
投票

确实,运行时似乎没有办法在管理模式和非管理模式之间切换。但这里有一个针对最相关常量的解决方法。它包括

Martin Prikryl 的解决方案

,但还有更多内容,似乎不属于最初的讨论: ; Written and compatible with Inno Setup 5.5.6 unicode #define MyAppName "[GAMENAME]" #define app "{pf}\" + MyAppPublisher + "\" + MyAppName + "\" #define IfPrivilegesRequiredLowestTryAdmin_YES_NO "YES" #define AppDirLimitToProgramFilesFolder_YES_NO "NO" [Setup] AppId={{A957445F-DB7F-4F72-974C-E2CDC11AA492}-{code:ElevationProgramName}-{code:GetAuthorIfUnelevated} PrivilegesRequired=lowest UninstallDisplayIcon={app}\{#MyAppExeName} UninstallDisplayName={#MyAppName} {code:ElevationProgramName} UsePreviousLanguage=no [Icons] ;--- first/second entry if increased rights (from Vista / up to XP), third for if limited rights ;Start menu shortcut (to start) Name: "{%programdata}\Microsoft\Windows\Start Menu\Programs\{#MyAppName}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Check: IsAdminLoggedOn; MinVersion: 6.0; Name: "{code:startmenupath}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Check: IsAdminLoggedOn; OnlyBelowVersion: 6.0; Name: "{group}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Check: "not IsAdminLoggedOn"; ;Start menu shortcut (for uninstalling) Name: "{%programdata}\Microsoft\Windows\Start Menu\Programs\{#MyAppName}\{#MyAppName} {code:UninstallAppTitle}"; Filename: "{uninstallexe}"; IconFilename: "{app}\Icons\Uninstall.ico"; Check: IsAdminLoggedOn; MinVersion: 6.0; Name: "{code:startmenupath}\{#MyAppName} {code:UninstallAppTitle}"; Filename: "{uninstallexe}"; IconFilename: "{app}\Icons\Uninstall.ico"; Check: IsAdminLoggedOn; OnlyBelowVersion: 6.0; Name: "{group}\{#MyAppName} {code:UninstallAppTitle}"; Filename: "{uninstallexe}"; IconFilename: "{app}\Icons\Uninstall.ico"; Check: "not IsAdminLoggedOn"; ;Desktop shortcut (to start) Name: "{sd}\Users\Public\Desktop\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: desktopicon; Check: IsAdminLoggedOn; MinVersion: 6.0; Name: "{%allusersprofile}\Desktop\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: desktopicon; Check: IsAdminLoggedOn; OnlyBelowVersion: 6.0; Name: "{userdesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: desktopicon; Check: "not IsAdminLoggedOn"; [CustomMessages] ProgramDirErrorX64=The path entered must be a subdirectory of:%n"%1" OR "%2" ProgramDirErrorX32=The path entered must be a subdirectory of:%n"%1" UserDirError=Not allowed: "%1"%nRestart setup with restricted rights or change the path. NoAdminAndOSTooOldforUAC=Elevated rights not available!%n%nSince Windows XP cannot start the UAC from the context, your account%nmust be a member of the Administrators group to be allowed to perform installations.%n%nThe installation wizard will now close. NoAdminAndOSTooOldforUACOfferLimitedInstall=Elevated rights not available!%n%nSince Windows XP cannot start the UAC from the context, your account%nmust be a member of the Administrators group to be allowed to perform installations.%nThe program can also be installed without administrator rights for the current user only.%n%nWould you like to continue with this installation under the condition? UninstallAppTitle=uninstall ___DummyThatLastMessageAtTheEndDoesNotHaveAnEmptyLine=Test [Code] // ------------------------------------------------------------- // VARIABLES THAT ARE IMPORTANT FOR [SETUP] function IsFilenameValid(Path: string): Boolean; begin if (Pos('\', Path) + Pos('/', Path) + Pos(':', Path) + Pos('*', Path) + Pos(ExpandConstant('?'), Path) + Pos('"', Path) + Pos('<', Path) + Pos('>', Path) + Pos('|', Path)) = 0 then begin Result := True; end else begin Result := False; end; end; function BeginsWith(SubText, Text: string): Boolean; var BeginStr: string; begin BeginStr := Copy(Text, 0, Length(SubText)); Result := (CompareText(SubText, BeginStr) = 0); end; function ElevationProgramName(Value: string): string; begin try if (IsAdminLoggedOn) then begin Result := '(All Users)'; end else begin Result := '(Current User)'; end; except end; end; procedure RegisterPreviousData(PreviousDataKey: Integer); var UninstallMode: string; begin // this will store the value under the specified key; except uninstaller you // can read the values stored this way in installer SetPreviousData(PreviousDataKey, 'UninstallMode', ElevationProgramName(UninstallMode)); end; function WbemQuery(WbemServices: Variant; Query: string): Variant; var WbemObjectSet: Variant; begin Result := Null; WbemObjectSet := WbemServices.ExecQuery(Query); if not VarIsNull(WbemObjectSet) and (WbemObjectSet.Count > 0) then begin Result := WbemObjectSet.ItemIndex(0); end; end; function RetrieveCurrentUsername: string; var Query: Variant; WbemLocator: Variant; WbemServices: Variant; ComputerSystem: Variant; WbemObjectSet: Variant; begin WbemLocator := CreateOleObject('WbemScripting.SWbemLocator'); WbemServices := WbemLocator.ConnectServer('.', 'root\CIMV2'); Query := 'SELECT UserName FROM Win32_ComputerSystem'; ComputerSystem := WbemQuery(WbemServices, Query); if not VarIsNull(ComputerSystem) then begin Result := ExtractFileName(ComputerSystem.UserName); //Userdomain := ExtractFilePath(ComputerSystem.UserName); //StringChangeEx(Userdomain, '\', '', True); end; end; function GetAuthorIfUnelevated(Value: string): string; begin if IsAdminLoggedOn then begin Result := ''; end else begin Result := RetrieveCurrentUsername; end; end; const CSIDL_COMMON_PROGRAMS = $0017; function startmenupath(Value: string): string; begin if not IsWindowsVistaOrNewer then begin Result := GetShellFolderByCSIDL(CSIDL_COMMON_PROGRAMS, True) + '\{#MyAppName}'; end; end; function UninstallAppTitle(Value: string): string; begin if IsFilenameValid(ExpandConstant('{cm:UninstallAppTitle}')) then begin Result := ExpandConstant('{cm:UninstallAppTitle}'); end else begin Result := 'uninstall'; end; end; // ------------------------------------------------------------- // ------------------------------------------------------------- // ERLAUBE DIE INSTALLATION NUR BEI KORREKTEM PFAD function AnythingAfterPrefix(S: string; Prefix: string): Boolean; begin Result := (Copy(S, 1, Length(Prefix)) = Prefix) and (Length(S) > Length(Prefix)); end; procedure OnDirEditChange(Sender: TObject); var MySecretDir1: string; MySecretDir2: string; Status2: string; Status1: string; SelectDirBrowseLabel: variant; PathErrorX64: string; PathErrorX32: string; begin if ('{#AppDirLimitToProgramFilesFolder_YES_NO}' = 'YES') then begin MySecretDir1 := ExpandConstant('{pf}'); if IsWin64 then MySecretDir2 := ExpandConstant('{pf64}'); SelectDirBrowseLabel := SetupMessage(msgSelectDirBrowseLabel); // ↓ Name ↓ %1 ↓ %2 if IsWin64 then PathErrorX64 := FmtMessage(CustomMessage('ProgramDirErrorX64'), [ExpandConstant('{pf}'+'\'), ExpandConstant('{pf64}'+'\')]); PathErrorX32 := FmtMessage(CustomMessage('ProgramDirErrorX32'), [ExpandConstant('{pf}'+'\')]); // CONDITION TO CHECK IF THE SELECTED INSTALLATION PATH IS A SUBORDINATOR OF %ProgramFiles% OR %ProgramW6432%: if WizardForm.NextButton.Enabled = True then Status1 := 'True'; if WizardForm.NextButton.Enabled = False then Status1 := 'False'; if not AnythingAfterPrefix(TrimRight(WizardDirValue), MySecretDir1) AND (WizardDirValue > MySecretDir1) then WizardForm.NextButton.Enabled := True; WizardForm.NextButton.Enabled := Pos(MySecretDir1, WizardDirValue) OR Pos(MySecretDir2, WizardDirValue) = 1; if not AnythingAfterPrefix(TrimRight(WizardDirValue), MySecretDir1 + '\') AND AnythingAfterPrefix(TrimRight(WizardDirValue + '\'), MySecretDir2) then WizardForm.NextButton.Enabled := False; if not (WizardDirValue <= MySecretDir2) AND AnythingAfterPrefix(WizardDirValue, MySecretDir2 + '\') then Wizardform.NextButton.Enabled := True; if WizardForm.NextButton.Enabled = True then Status2 := 'True'; if WizardForm.NextButton.Enabled = False then Status2 := 'False'; if (Status1 = 'True') AND (Status2 = 'False') then begin if IsWin64 then WizardForm.SelectDirBrowseLabel.Caption := PathErrorX64; if not IsWin64 then WizardForm.SelectDirBrowseLabel.Caption := PathErrorX32; WizardForm.SelectDirBrowseLabel.Font.Style := [fsBold]; // Use bold font WizardForm.DirEdit.Color := clRed; // Use red background color for the input field of the program path end; if (Status1 = 'False') AND (Status2 = 'True') then begin WizardForm.SelectDirBrowseLabel.Caption := SelectDirBrowseLabel; WizardForm.SelectDirBrowseLabel.Font.Style := []; // Use normal font WizardForm.DirEdit.Color := $fffbff; // -Use -FontColor- for the input field of the program path (here: white) end; end; if (IsAdminLoggedOn) AND not ('{#AppDirLimitToProgramFilesFolder_YES_NO}' = 'YES') then begin MySecretDir1 := ExtractFilePath(ExpandConstant('{%userprofile}')); SelectDirBrowseLabel := SetupMessage(msgSelectDirBrowseLabel); // ↓ Name ↓ %1 ↓ %2 PathErrorX32 := FmtMessage(CustomMessage('UserDirError'), [ExtractFilePath(ExpandConstant('{%userprofile}'))]); // REQUIRE TO CHECK IF THE SELECTED INSTALLATION PATH IS SUBORDINATE TO %systemdrive%\Users\: if WizardForm.NextButton.Enabled = True then Status1 := 'True'; if WizardForm.NextButton.Enabled = False then Status1 := 'False'; if BeginsWith(ExtractFilePath(ExpandConstant('{%userprofile}')), WizardDirValue) OR (Copy(ExtractFilePath(ExpandConstant('{%userprofile}')), 0, Length(ExtractFilePath(ExpandConstant('{%userprofile}'))) -1) = WizardDirValue) then begin WizardForm.NextButton.Enabled := False; end else begin WizardForm.NextButton.Enabled := True; end; if WizardForm.NextButton.Enabled = True then Status2 := 'True'; if WizardForm.NextButton.Enabled = False then Status2 := 'False'; if (Status1 = 'True') AND (Status2 = 'False') then begin WizardForm.SelectDirBrowseLabel.Caption := PathErrorX32; WizardForm.SelectDirBrowseLabel.Font.Style := [fsBold]; // Use bold font WizardForm.DirEdit.Color := clRed; // Use red background color for the input field of the program path end; if (Status1 = 'False') AND (Status2 = 'True') then begin WizardForm.SelectDirBrowseLabel.Caption := SelectDirBrowseLabel; WizardForm.SelectDirBrowseLabel.Font.Style := []; // Use normal font WizardForm.DirEdit.Color := $fffbff; // -Use -FontColor- for the input field of the program path (here: white) end; end; end; // ------------------------------------------------------------- // ------------------------------------------------------------- // IF IfPrivilegesRequiredLowestTryAdmin_YES_NO = "YES", TRY FIRST WITH ADMIN RIGHTS AND IF UAC = NO, START WITH USER RIGHTS function IsWinVista: Boolean; begin Result := (GetWindowsVersion >= $06000000); end; function HaveWriteAccessToApp: Boolean; var FileName: string; begin FileName := AddBackslash(WizardDirValue) + 'writetest.tmp'; Result := SaveStringToFile(FileName, 'test', False); if Result then begin Log(Format( 'Have write access to the last installation path [%s]', [WizardDirValue])); DeleteFile(FileName); end else begin Log(Format('Does not have write access to the last installation path [%s]', [ WizardDirValue])); end; end; procedure ExitProcess(uExitCode: UINT); external '[email protected] stdcall'; function ShellExecute(hwnd: HWND; lpOperation: string; lpFile: string; lpParameters: string; lpDirectory: string; nShowCmd: Integer): THandle; external '[email protected] stdcall'; function Elevate: Boolean; var I: Integer; RetVal: Integer; Params: string; S: string; begin { Collect current instance parameters } for I := 1 to ParamCount do begin S := ParamStr(I); { Unique log file name for the elevated instance } if CompareText(Copy(S, 1, 5), '/LOG=') = 0 then begin S := S + '-elevated'; end; { Do not pass our /SL5 switch } if CompareText(Copy(S, 1, 5), '/SL5=') <> 0 then begin Params := Params + AddQuotes(S) + ' '; end; end; { ... and add selected language } Params := Params + '/ALLUSERS=YES ' + '/LANG=' + ActiveLanguage; Log(Format('Elevating setup with parameters [%s]', [Params])); RetVal := ShellExecute(0, 'runas', ExpandConstant('{srcexe}'), Params, '', SW_SHOW); Log(Format('Running elevated setup returned [%d]', [RetVal])); Result := (RetVal > 32); { if elevated executing of this setup succeeded, then... } if Result then begin Log('Elevation succeeded'); { exit this non-elevated setup instance } //Beende Setup mit "Abort()" anstatt mit "ExitProcess(0)", damit temporäre Dateien trotzdme noch gelöscht werden (saubere Methode) //ExitProcess(0); Abort() end else begin Log(Format('Elevation failed [%s]', [SysErrorMessage(RetVal)])); end; end; const PathToRegistryUninstall = '\Software\Microsoft\Windows\CurrentVersion\Uninstall\' + '{#emit StringChange(SetupSetting("AppId"),"{{","{")}' + '_is1'; // ------------------------------------------------------------- procedure InitializeWizard; var NoClicked: boolean; S: string; Upgrade: Boolean; RightsChecked: boolean; message2: string; WelcomeLabelCombinedText: string; message: string; WelcomeLabel3: TNewStaticText; Userprofile: String; begin begin WizardForm.DirEdit.OnChange := @OnDirEditChange; if ('{#IfPrivilegesRequiredLowestTryAdmin_YES_NO}' = 'YES') then begin Upgrade := RegQueryStringValue(HKLM, PathToRegistryUninstall, '{app}', S) or RegQueryStringValue(HKCU, PathToRegistryUninstall, '{app}', S); { elevate } if not IsWinVista then begin Log(Format('This version of Windows [%x] does not support elevation', [ GetWindowsVersion])); end else if IsAdminLoggedOn then begin Log('Running elevated'); //MsgBox(ExpandConstant('At this point -AdministrativeInstallMode- should have been set, if something like this had been possible: {#SetupSetting("PrivilegesRequired")}'), mbInformation, mb_OK); end else begin Log('Running non-elevated'); if Upgrade then begin if not HaveWriteAccessToApp then begin Elevate; end; end else begin if not Elevate then //UAC answered with "No".' begin if MsgBox(ExpandConstant(CustomMessage('UACDeniedOptionalUserMode')), mbConfirmation, MB_YESNO or MB_DEFBUTTON1) = IDNO then begin //Question about non-admin installation also answered with "No" NoClicked := true Abort() end else WizardForm.DirEdit.Text := ExpandConstant('{localappdata}\{#MyAppName}'); Log(Format('Falling back to local application user folder [%s]', [ WizardForm.DirEdit.Text])); end; end; end; end; // Treat such old OS's like Windows 2000/XP, which do not yet have a UAC for responding: RightsChecked := not(IsAdminLoggedOn); if (RightsChecked = true) AND (not IsWindowsVistaOrNewer) then begin if ('{#IfPrivilegesRequiredLowestTryAdmin_YES_NO}' = 'NO') then begin //Question about non-admin installation also answered with "No" NoClicked := true MsgBox(ExpandConstant('{cm:NoAdminAndOSTooOldforUAC}'), mbCriticalError, MB_OK); Abort() end; if ('{#IfPrivilegesRequiredLowestTryAdmin_YES_NO}' = 'YES') then begin message2 := CustomMessage('NoAdminAndOSTooOldforUACOfferLimitedInstall'); if MsgBox(ExpandConstant(message2), mbError, MB_YESNO or MB_DEFBUTTON1) = IDYES then begin WizardForm.DirEdit.Text := ExpandConstant('{localappdata}\{#MyAppName}'); Log(Format('Falling back to local application user folder [%s]', [ WizardForm.DirEdit.Text])); end else //XP query rejected after installation due to restricted rights: Abort() end; end; end; // ------------------------------------------------------------- // MANUALLY CHANGE UNINSTALL-REGISTRY PATH FROM HKCU TO HKLM var AppId: string; function MoveHKCUUninstallKeyToHKLM: Boolean; var UninstallKey: string; I: Integer; ValueNames: TArrayOfString; ValueName: string; ValueStr: string; ValueDWord: Cardinal; begin if ExpandConstant('{{#emit StringChange(SetupSetting("AppId"),"{{","{")}') <> '' then begin AppId := ExpandConstant('{{#emit StringChange(SetupSetting("AppId"),"{{","{")}'); end else begin AppId := ExpandConstant('{{#emit StringChange(SetupSetting("AppName"),"{{","{")}'); end; Result := False; if AppId = '' then begin Log('Cannot identify AppId'); end else begin UninstallKey := 'Software\Microsoft\Windows\CurrentVersion\Uninstall\' + AppId + '_is1'; Log(Format( 'AppId identified as "%s", using uninstall key "%s"', [AppId, UninstallKey])); if not RegKeyExists(HKCU, UninstallKey) then begin Log('HKCU uninstall key not found'); end else if RegKeyExists(HKLM, UninstallKey) then begin Log('HKLM uninstall key exists already'); end else begin Log('HKCU uninstall key found and HKLM key not exists yet'); if not RegGetValueNames(HKCU, UninstallKey, ValueNames) then begin Log('Cannot list uninstall key values'); end else begin I := 0; Result := True; while (I < GetArrayLength(ValueNames)) and Result do begin ValueName := ValueNames[I]; if RegQueryStringValue(HKCU, UninstallKey, ValueName, ValueStr) then begin if not RegWriteStringValue(HKLM, UninstallKey, ValueName, ValueStr) then begin Log(Format('Error moving "%s" string value', [ValueName])); Result := False; end else begin Log(Format('Moved "%s" string value', [ValueName])); end; end else if RegQueryDWordValue(HKCU, UninstallKey, ValueName, ValueDWord) then begin if not RegWriteDWordValue(HKLM, UninstallKey, ValueName, ValueDWord) then begin Log(Format('Error moving "%s" dword value', [ValueName])); Result := False; end else begin Log(Format('Moved "%s" dword value', [ValueName])); end; end else begin { All uninstall values written by Inno Setup are either string or dword } Log(Format('Value "%s" is neither string nor dword', [ValueName])); Result := False; end; Inc(I); end; if Result then begin if not RegDeleteKeyIncludingSubkeys(HKCU, UninstallKey) then begin Log('Error removing HKCU uninstall key'); Result := False; end else begin Log('Removed HKCU uninstall key'); end; end; if not Result then begin if not RegDeleteKeyIncludingSubkeys(HKCU, UninstallKey) then begin Log('Failed to move uninstall key to HKLM, ' + 'and also failed to rollback the changes'); end else begin Log('Failed to move uninstall key to HKLM, rolled back the changes'); end; end; end; end; end; end; // ------------------------------------------------------------- procedure CurStepChanged(CurStep: TSetupStep); var InstallMode: string; begin InstallMode := GetPreviousData('UninstallMode', '(Current User)'); if (CurStep = ssPostInstall) AND (InstallMode = '(All Users)') then begin MoveHKCUUninstallKeyToHKLM; end; end; // ------------------------------------------------------------- // REMOVE THE UNINSTALL FLAG IF ELEVATED RIGHTS procedure CurUninstallStepChanged(CurUninstallStep: TUninstallStep); var SettingsFile: string; SettingsFolder: string; UninstallMode: string; InstallUsername: string; CurrentUsername: string; CurrentSID: string; begin if CurUninstallStep = usUninstall then begin UninstallMode := GetPreviousData('UninstallMode', '(Current User)'); AppId := ExpandConstant('{#SetupSetting("AppId")}'); if (UninstallMode = '(All Users)') then begin StringChangeEx(AppId, '(Current User)', '(All Users)', True); RegDeleteKeyIncludingSubkeys(HKLM, 'Software\Microsoft\Windows\CurrentVersion\Uninstall\' + AppId + '_is1'); end; if (UninstallMode = '(Current User)') then begin StringChangeEx(AppId, '(All Users)', '(Current User)', True); //When calling the uninstaller with elevated rights, HKCU is not removed. To do this here, I would need the SID of the currently logged on user - but there is no suitable solution for this (minor thing, so do not deal with it further) //RegDeleteKeyIncludingSubkeys(HKU, '???\Software\Microsoft\Windows\CurrentVersion\Uninstall\' + AppId + '_is1'); end; end; end; // -------------------------------------------------------------

也许有人会发现这很有用,特别是因为它被设计为向后兼容到 Windows 2000。

© www.soinside.com 2019 - 2024. All rights reserved.