Migrated away from .idt tables in favor of a single .sql file that updates the MSI post-creation.
Added support for installing a `preload.json` file by setting the MSI option `INSTALL_PRELOAD=true` and then placing a `preload.json` file in the same folder as where the MSI is located.
This commit is contained in:
@@ -560,19 +560,26 @@ public static partial class Command
|
||||
|
||||
await ProcessHelper.Execute(wixArgs, workingDirectory: buildRoot);
|
||||
|
||||
// Apply any installer data tables present
|
||||
// On Windows, the wxs files include the tables, but on Linux/MacOS, we need to apply them separately
|
||||
// because wixl does not support the tables directly
|
||||
// Apply post-wixl MSI table customizations on Linux/macOS. On Windows the
|
||||
// real WiX toolchain handles all of these natively via WXS elements
|
||||
if (!OperatingSystem.IsWindows())
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(rtcfg.Configuration.Commands.MsiBuild))
|
||||
{
|
||||
Console.WriteLine("WARNING: msiBuild not found, skipping IDT file injection.");
|
||||
Console.WriteLine("WARNING: msiBuild not found, skipping SQL file injection.");
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var idtFile in Directory.GetFiles(resourcesSubDir, "*.idt"))
|
||||
await ProcessHelper.Execute([rtcfg.Configuration.Commands.MsiBuild, msiFile, "-i", idtFile]);
|
||||
foreach (var sqlFile in Directory.GetFiles(resourcesSubDir, "*.sql"))
|
||||
{
|
||||
var queries = (await File.ReadAllLinesAsync(sqlFile))
|
||||
.Select(line => line.Trim())
|
||||
.Where(line => line.Length > 0 && !line.StartsWith("--"))
|
||||
.ToArray();
|
||||
|
||||
foreach (var query in queries)
|
||||
await ProcessHelper.Execute([rtcfg.Configuration.Commands.MsiBuild, msiFile, "-q", query]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -35,6 +35,12 @@
|
||||
<Property Id="FORSERVICE" Value="false" Secure="yes" />
|
||||
<Property Id="NOSCRIPT" Value="false" Secure="yes" />
|
||||
|
||||
<!-- Opt-in switch for installing a preload.json -->
|
||||
<Property Id="INSTALL_PRELOAD" Secure="yes" />
|
||||
|
||||
<!-- Property from SetPreloadSource pointing to [SourceDir] -->
|
||||
<Property Id="PRELOAD_SOURCE" Secure="yes" />
|
||||
|
||||
<Directory Id="TARGETDIR" Name="SourceDir">
|
||||
<Directory Id="ProgramMenuFolder" Name="Programs"/>
|
||||
<Directory Id="DesktopFolder" Name="Desktop"/>
|
||||
@@ -87,6 +93,9 @@
|
||||
<!-- Stop and start service must be deferred actions between InstallInitialize and InstallFinalize -->
|
||||
<Custom Action="StopService" After="InstallInitialize">SERVICE_WAS_STOPPED=1 AND NOT UPGRADINGPRODUCTCODE</Custom>
|
||||
<Custom Action="StartService" After="InstallFiles">SERVICE_WAS_STOPPED=1 AND NOT REMOVE~="ALL" AND NOT UPGRADINGPRODUCTCODE</Custom>
|
||||
|
||||
<!-- Resolves [SourceDir] into PRELOAD_SOURCE -->
|
||||
<Custom Action="SetPreloadSource" After="CostFinalize">INSTALL_PRELOAD="true" AND NOT Installed</Custom>
|
||||
</InstallExecuteSequence>
|
||||
|
||||
<Property Id="PREVIOUSVERSIONSINSTALLED" Secure="yes" />
|
||||
@@ -119,5 +128,12 @@
|
||||
JScriptCall="CustomAction"
|
||||
Execute="deferred"
|
||||
Return="check" />
|
||||
|
||||
<!-- Resolves [SourceDir] into the PRELOAD_SOURCE property -->
|
||||
<CustomAction Id="SetPreloadSource"
|
||||
Property="PRELOAD_SOURCE"
|
||||
Value="[SourceDir]"
|
||||
Execute="immediate"
|
||||
Return="check" />
|
||||
</Product>
|
||||
</Wix>
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
-- Post-wixl MSI table customizations for the Agent installer.
|
||||
--
|
||||
-- The file is processed by Command.CreatePackage.cs after wixl produces the
|
||||
-- MSI: each non-empty, non-comment line is run as a single `msibuild -q`
|
||||
-- query. Statements are executed in order; lines beginning with `--` and
|
||||
-- blank lines are ignored.
|
||||
--
|
||||
-- Why this exists: wixl 0.106 does not understand WiX elements such as
|
||||
-- <util:PermissionEx> or <CopyFile> (on a Component) - it silently drops
|
||||
-- them or fails to build. On Windows the real WiX toolchain emits the
|
||||
-- corresponding tables natively from the WXS sources; on Linux/macOS we
|
||||
-- have to author the rows directly. Statements that touch tables wixl
|
||||
-- already emits (RemoveFile, InstallExecuteSequence) must use INSERT only,
|
||||
-- never CREATE, because `msibuild -q "CREATE TABLE foo"` against an
|
||||
-- existing table fails.
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- MsiLockPermissionsEx: ACLs for the install dir. Replaces the WiX
|
||||
-- <util:PermissionEx> element on SecureInstallDirComp that wixl cannot parse.
|
||||
-- ---------------------------------------------------------------------------
|
||||
CREATE TABLE `MsiLockPermissionsEx` (`MsiLockPermissionsEx` CHAR(72) NOT NULL, `LockObject` CHAR(72) NOT NULL, `Table` CHAR(32) NOT NULL, `SDDLText` CHAR(255) NOT NULL, `Condition` CHAR(255) PRIMARY KEY `MsiLockPermissionsEx`, `LockObject`, `Table`)
|
||||
INSERT INTO `MsiLockPermissionsEx` (`MsiLockPermissionsEx`, `LockObject`, `Table`, `SDDLText`) VALUES ('SecureFolderACLs', 'INSTALLLOCATION', 'CreateFolder', 'D:P(A;OICI;FA;;;SY)(A;OICI;FA;;;BA)(A;OICI;GRFX;;;BU)')
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- MoveFile: copies preload.json from the directory containing the MSI
|
||||
-- (resolved into PRELOAD_SOURCE by SetPreloadSource) into INSTALLLOCATION
|
||||
-- when the user passes INSTALL_PRELOAD=true on the msiexec command line.
|
||||
-- The MoveFile row is keyed to the always-installed Duplicati.Agent.exe
|
||||
-- component so it ships with the rest of the binaries and follows their
|
||||
-- install/uninstall lifecycle. Options=0 means copy (not move). Replaces
|
||||
-- the WiX <CopyFile> element that wixl cannot parse on a Component without
|
||||
-- a <File> child.
|
||||
--
|
||||
-- The standard MoveFiles action that processes this table is scheduled
|
||||
-- further below in this file.
|
||||
-- ---------------------------------------------------------------------------
|
||||
CREATE TABLE `MoveFile` (`FileKey` CHAR(72) NOT NULL, `Component_` CHAR(72) NOT NULL, `SourceName` CHAR(255) LOCALIZABLE, `DestName` CHAR(255) LOCALIZABLE, `SourceFolder` CHAR(72), `DestFolder` CHAR(72) NOT NULL, `Options` SHORT NOT NULL PRIMARY KEY `FileKey`)
|
||||
INSERT INTO `MoveFile` (`FileKey`, `Component_`, `SourceName`, `DestName`, `SourceFolder`, `DestFolder`, `Options`) VALUES ('CopyPreloadJson', 'Duplicati.Agent.exe', 'preload.json', 'preload.json', 'PRELOAD_SOURCE', 'INSTALLLOCATION', 0)
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- RemoveFile: removes preload.json from INSTALLLOCATION on uninstall
|
||||
-- (InstallMode=2 = msidbRemoveFileInstallModeOnRemove). The RemoveFile
|
||||
-- table is already created by wixl so we only INSERT here.
|
||||
-- ---------------------------------------------------------------------------
|
||||
INSERT INTO `RemoveFile` (`FileKey`, `Component_`, `FileName`, `DirProperty`, `InstallMode`) VALUES ('RemovePreloadJson', 'Duplicati.Agent.exe', 'preload.json', 'INSTALLLOCATION', 2)
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- InstallExecuteSequence: schedule the standard ResolveSource and MoveFiles
|
||||
-- actions so the MoveFile row above is actually processed. wixl does not
|
||||
-- auto-insert these standard actions because their respective tables are
|
||||
-- empty at wixl build time. Both are gated by INSTALL_PRELOAD="true" so they
|
||||
-- only fire when the user explicitly opts in.
|
||||
-- ResolveSource @ 850 - populates [SourceDir] before SetPreloadSource
|
||||
-- consumes it (SetPreloadSource itself is declared in the WXS at ~1001
|
||||
-- via <Custom Action="SetPreloadSource" After="CostFinalize">).
|
||||
-- MoveFiles @ 3700 - canonical position between RemoveFiles (3500)
|
||||
-- and InstallFiles (4000).
|
||||
-- ---------------------------------------------------------------------------
|
||||
INSERT INTO `InstallExecuteSequence` (`Action`, `Condition`, `Sequence`) VALUES ('ResolveSource', 'INSTALL_PRELOAD="true" AND NOT Installed', 850)
|
||||
INSERT INTO `InstallExecuteSequence` (`Action`, `Condition`, `Sequence`) VALUES ('MoveFiles', 'INSTALL_PRELOAD="true"', 3700)
|
||||
@@ -1,4 +0,0 @@
|
||||
MsiLockPermissionsEx LockObject Table SDDLText Condition
|
||||
s72 s72 s32 s255 S255
|
||||
MsiLockPermissionsEx MsiLockPermissionsEx LockObject Table
|
||||
SecureFolderACLs INSTALLLOCATION CreateFolder D:P(A;OICI;FA;;;SY)(A;OICI;FA;;;BA)(A;OICI;GRFX;;;BU)
|
||||
@@ -54,6 +54,12 @@
|
||||
elevated execution sequence. -->
|
||||
<Property Id="INSTALL_TLS_CERTS" Secure="yes" />
|
||||
|
||||
<!-- Opt-in switch for installing a preload.json -->
|
||||
<Property Id="INSTALL_PRELOAD" Secure="yes" />
|
||||
|
||||
<!-- Property from SetPreloadSource pointing to [SourceDir] -->
|
||||
<Property Id="PRELOAD_SOURCE" Secure="yes" />
|
||||
|
||||
<!-- Set to "true" by InstallOptionsDlg when the user opts into installing
|
||||
Duplicati as a Windows Service. Currently used only as a UI hint;
|
||||
the actual server-side behaviour is driven by the action state of
|
||||
@@ -90,7 +96,7 @@
|
||||
<Directory Id="INSTALLLOCATION" Name="Duplicati 2" >
|
||||
<Component Id="SecureInstallDirComp" Guid="7C52A563-7182-411E-8B57-79F881775E9B">
|
||||
<CreateFolder>
|
||||
<!-- WiX supports permssions, wixl uses a .idt table -->
|
||||
<!-- WiX supports permssions, wixl uses sql -->
|
||||
<?if $(var.BuildEnv) = "Windows" ?>
|
||||
<util:PermissionEx Sddl="D:P(A;OICI;FA;;;SY)(A;OICI;FA;;;BA)(A;OICI;GRFX;;;BU)" xmlns:util="http://schemas.microsoft.com/wix/UtilExtension"/>
|
||||
<?endif?>
|
||||
@@ -105,8 +111,9 @@
|
||||
Two registry keys are managed here:
|
||||
* Service - holds the Installed=1 keypath value AND
|
||||
the runtime-generated InitPassword value.
|
||||
The key's ACL is hardened by
|
||||
MsiLockPermissionsEx.idt.
|
||||
The key's ACL is hardened by the
|
||||
MsiLockPermissionsEx row in
|
||||
InstallTables.sql.
|
||||
|
||||
* ServiceState - holds the BootstrapApplied sentinel that
|
||||
the service writes once it has applied
|
||||
@@ -126,8 +133,8 @@
|
||||
first start. -->
|
||||
<RegistryKey Root="HKLM" Key="$(var.SoftwareKey)\DuplicatiTeam\Duplicati\Service">
|
||||
<!-- Id is fixed (not auto-generated) so the
|
||||
MsiLockPermissionsEx.idt row that hardens this key's ACL
|
||||
can reference a stable row primary key. -->
|
||||
MsiLockPermissionsEx row in InstallTables.sql that hardens
|
||||
this key's ACL can reference a stable row primary key. -->
|
||||
<RegistryValue Id="ServiceInstalledMarker" Name="Installed" Value="1" Type="string" KeyPath="yes" />
|
||||
<RegistryValue Id="ServiceInitPasswordValue" Name="InitPassword" Value="[DUPLICATI_SERVICE_PASSWORD]" Type="string" />
|
||||
</RegistryKey>
|
||||
@@ -141,7 +148,8 @@
|
||||
<RegistryKey Root="HKLM" Key="$(var.SoftwareKey)\DuplicatiTeam\Duplicati\ServiceState">
|
||||
<RegistryValue Id="ServiceStateInstalledMarker" Name="Installed" Value="1" Type="string" />
|
||||
<!-- Make sure we remove the BootstrapApplied value if it exists,
|
||||
which is handled by RemoveRegistry.idt on non-Windows.
|
||||
which is handled by the RemoveRegistry row in
|
||||
InstallTables.sql on non-Windows.
|
||||
The removal is required to ensure we do not pick up a stale value -->
|
||||
<?if $(var.BuildEnv) = "Windows" ?>
|
||||
<RemoveRegistryValue Id="RemoveStaleBootstrapApplied" Name="BootstrapApplied" Action="removeOnInstall" />
|
||||
@@ -238,6 +246,9 @@
|
||||
<Custom Action="SetNoHostedServerArg" Before="CreateShortcuts">&DuplicatiServiceFeature=3</Custom>
|
||||
<Custom Action="ClearNoHostedServerArg" Before="CreateShortcuts">NOT (&DuplicatiServiceFeature=3)</Custom>
|
||||
|
||||
<!-- Resolves [SourceDir] into PRELOAD_SOURCE -->
|
||||
<Custom Action="SetPreloadSource" After="CostFinalize">INSTALL_PRELOAD="true" AND NOT Installed</Custom>
|
||||
|
||||
<!--
|
||||
Service install pipeline (only when the service feature is freshly
|
||||
installed, gated by the same condition):
|
||||
@@ -251,9 +262,9 @@
|
||||
the InitPassword value into the protected Service registry key
|
||||
owned by DuplicatiServiceMarkerComponent, substituting in the
|
||||
current value of DUPLICATI_SERVICE_PASSWORD. The key's ACL is
|
||||
hardened by a row in the hand-authored MsiLockPermissionsEx.idt
|
||||
table (injected by msibuild after wixl runs) so non-admin users
|
||||
cannot read the password value.
|
||||
hardened by a row in the hand-authored MsiLockPermissionsEx
|
||||
table from InstallTables.sql (injected by msibuild after wixl
|
||||
runs) so non-admin users cannot read the password value.
|
||||
3. InstallDuplicatiService (sequenced AFTER WriteRegistryValues so
|
||||
the password is already in the registry before SCM starts the
|
||||
service) registers the Duplicati Windows Service. The service
|
||||
@@ -364,6 +375,13 @@
|
||||
Execute="immediate"
|
||||
Return="check" />
|
||||
|
||||
<!-- Resolves [SourceDir] into the PRELOAD_SOURCE property -->
|
||||
<CustomAction Id="SetPreloadSource"
|
||||
Property="PRELOAD_SOURCE"
|
||||
Value="[SourceDir]"
|
||||
Execute="immediate"
|
||||
Return="check" />
|
||||
|
||||
<!-- Service install/uninstall custom actions.
|
||||
The installer invokes Duplicati.WindowsService.exe via a JScript
|
||||
helper (SilentRun.js) so the console window stays hidden.
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
-- Post-wixl MSI table customizations for the TrayIcon installer.
|
||||
--
|
||||
-- The file is processed by Command.CreatePackage.cs after wixl produces the
|
||||
-- MSI: each non-empty, non-comment line is run as a single `msibuild -q`
|
||||
-- query. Statements are executed in order; lines beginning with `--` and
|
||||
-- blank lines are ignored.
|
||||
--
|
||||
-- This enables certain customizations that wixl cannot handle natively.
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- MsiLockPermissionsEx: ACLs for the install dir and the protected
|
||||
-- Service / ServiceState registry keys created by
|
||||
-- DuplicatiServiceMarkerComponent. Replaces the WiX <util:PermissionEx>
|
||||
-- elements that wixl cannot parse.
|
||||
-- ---------------------------------------------------------------------------
|
||||
CREATE TABLE `MsiLockPermissionsEx` (`MsiLockPermissionsEx` CHAR(72) NOT NULL, `LockObject` CHAR(72) NOT NULL, `Table` CHAR(32) NOT NULL, `SDDLText` CHAR(255) NOT NULL, `Condition` CHAR(255) PRIMARY KEY `MsiLockPermissionsEx`, `LockObject`, `Table`)
|
||||
INSERT INTO `MsiLockPermissionsEx` (`MsiLockPermissionsEx`, `LockObject`, `Table`, `SDDLText`) VALUES ('SecureFolderACLs', 'INSTALLLOCATION', 'CreateFolder', 'D:P(A;OICI;FA;;;SY)(A;OICI;FA;;;BA)(A;OICI;GRFX;;;BU)')
|
||||
INSERT INTO `MsiLockPermissionsEx` (`MsiLockPermissionsEx`, `LockObject`, `Table`, `SDDLText`) VALUES ('SecureServiceRegKey', 'ServiceInstalledMarker', 'Registry', 'D:P(A;OICI;KA;;;SY)(A;OICI;KA;;;BA)')
|
||||
INSERT INTO `MsiLockPermissionsEx` (`MsiLockPermissionsEx`, `LockObject`, `Table`, `SDDLText`) VALUES ('OpenServiceStateRegKey', 'ServiceStateInstalledMarker', 'Registry', 'D:P(A;OICI;KA;;;SY)(A;OICI;KA;;;BA)(A;OICI;KR;;;AU)(A;OICI;KR;;;BU)')
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- RemoveRegistry: deletes the BootstrapApplied sentinel value on install so
|
||||
-- a stale value from a previous install does not get re-picked-up by the
|
||||
-- service. Replaces the WiX <RemoveRegistryValue Action="removeOnInstall"/>
|
||||
-- inside DuplicatiServiceMarkerComponent (wixl does not emit this).
|
||||
-- ---------------------------------------------------------------------------
|
||||
CREATE TABLE `RemoveRegistry` (`RemoveRegistry` CHAR(72) NOT NULL, `Root` SHORT NOT NULL, `Key` CHAR(255) NOT NULL LOCALIZABLE, `Name` CHAR(255) LOCALIZABLE, `Component_` CHAR(72) NOT NULL PRIMARY KEY `RemoveRegistry`)
|
||||
INSERT INTO `RemoveRegistry` (`RemoveRegistry`, `Root`, `Key`, `Name`, `Component_`) VALUES ('RemoveStaleBootstrapApplied', 2, 'SOFTWARE\DuplicatiTeam\Duplicati\ServiceState', 'BootstrapApplied', 'DuplicatiServiceMarkerComponent')
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- MoveFile: copies preload.json from the directory containing the MSI
|
||||
-- (resolved into PRELOAD_SOURCE by SetPreloadSource) into INSTALLLOCATION
|
||||
-- when the user passes INSTALL_PRELOAD=true on the msiexec command line.
|
||||
-- The MoveFile row is keyed to the always-installed Duplicati.GUI.TrayIcon.exe
|
||||
-- component so it ships with the rest of the binaries and follows their
|
||||
-- install/uninstall lifecycle. Options=0 means copy (not move). Replaces the
|
||||
-- WiX <CopyFile> element that wixl cannot parse on a Component without a
|
||||
-- <File> child.
|
||||
--
|
||||
-- The standard MoveFiles action that processes this table is scheduled
|
||||
-- further below in this file.
|
||||
-- ---------------------------------------------------------------------------
|
||||
CREATE TABLE `MoveFile` (`FileKey` CHAR(72) NOT NULL, `Component_` CHAR(72) NOT NULL, `SourceName` CHAR(255) LOCALIZABLE, `DestName` CHAR(255) LOCALIZABLE, `SourceFolder` CHAR(72), `DestFolder` CHAR(72) NOT NULL, `Options` SHORT NOT NULL PRIMARY KEY `FileKey`)
|
||||
INSERT INTO `MoveFile` (`FileKey`, `Component_`, `SourceName`, `DestName`, `SourceFolder`, `DestFolder`, `Options`) VALUES ('CopyPreloadJson', 'Duplicati.GUI.TrayIcon.exe', 'preload.json', 'preload.json', 'PRELOAD_SOURCE', 'INSTALLLOCATION', 0)
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- RemoveFile: removes preload.json from INSTALLLOCATION on uninstall
|
||||
-- (InstallMode=2 = msidbRemoveFileInstallModeOnRemove). The RemoveFile
|
||||
-- table is already created by wixl so we only INSERT here. Replaces the
|
||||
-- WiX <RemoveFile> element under the preload component.
|
||||
-- ---------------------------------------------------------------------------
|
||||
INSERT INTO `RemoveFile` (`FileKey`, `Component_`, `FileName`, `DirProperty`, `InstallMode`) VALUES ('RemovePreloadJson', 'Duplicati.GUI.TrayIcon.exe', 'preload.json', 'INSTALLLOCATION', 2)
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- InstallExecuteSequence: schedule the standard ResolveSource and MoveFiles
|
||||
-- actions so the MoveFile row above is actually processed. wixl does not
|
||||
-- auto-insert these standard actions because their respective tables are
|
||||
-- empty at wixl build time. Both are gated by INSTALL_PRELOAD="true" so they
|
||||
-- only fire when the user explicitly opts in.
|
||||
-- ResolveSource @ 850 - populates [SourceDir] before SetPreloadSource
|
||||
-- consumes it (SetPreloadSource itself is declared in the WXS at ~1001
|
||||
-- via <Custom Action="SetPreloadSource" After="CostFinalize">).
|
||||
-- MoveFiles @ 3700 - canonical position between RemoveFiles (3500)
|
||||
-- and InstallFiles (4000).
|
||||
-- ---------------------------------------------------------------------------
|
||||
INSERT INTO `InstallExecuteSequence` (`Action`, `Condition`, `Sequence`) VALUES ('ResolveSource', 'INSTALL_PRELOAD="true" AND NOT Installed', 850)
|
||||
INSERT INTO `InstallExecuteSequence` (`Action`, `Condition`, `Sequence`) VALUES ('MoveFiles', 'INSTALL_PRELOAD="true"', 3700)
|
||||
@@ -1,6 +0,0 @@
|
||||
MsiLockPermissionsEx LockObject Table SDDLText Condition
|
||||
s72 s72 s32 s255 S255
|
||||
MsiLockPermissionsEx MsiLockPermissionsEx LockObject Table
|
||||
SecureFolderACLs INSTALLLOCATION CreateFolder D:P(A;OICI;FA;;;SY)(A;OICI;FA;;;BA)(A;OICI;GRFX;;;BU)
|
||||
SecureServiceRegKey ServiceInstalledMarker Registry D:P(A;OICI;KA;;;SY)(A;OICI;KA;;;BA)
|
||||
OpenServiceStateRegKey ServiceStateInstalledMarker Registry D:P(A;OICI;KA;;;SY)(A;OICI;KA;;;BA)(A;OICI;KR;;;AU)(A;OICI;KR;;;BU)
|
||||
@@ -1,4 +0,0 @@
|
||||
RemoveRegistry Root Key Name Component_
|
||||
s72 i2 l255 L255 s72
|
||||
RemoveRegistry RemoveRegistry
|
||||
RemoveStaleBootstrapApplied 2 SOFTWARE\DuplicatiTeam\Duplicati\ServiceState BootstrapApplied DuplicatiServiceMarkerComponent
|
||||
Reference in New Issue
Block a user