sqlinsight-pro/installer/SQLInsight.wxs
Søren Eggert Lundsteen Olsen 6d755aa3d0
Some checks failed
deploy community / deploy (push) Successful in 1s
deploy hub / deploy (push) Successful in 19s
CI / Build og unit tests (push) Successful in 8s
CI / MSI-installer (push) Successful in 15s
CI / Integration (mcr.microsoft.com/mssql/server:2017-latest) (push) Has been cancelled
CI / Integration (mcr.microsoft.com/mssql/server:2019-latest) (push) Has been cancelled
CI / Integration (mcr.microsoft.com/mssql/server:2022-latest) (push) Has been cancelled
CI / Integration (mcr.microsoft.com/mssql/server:2025-latest) (push) Has been cancelled
Installer creates the repository database, logins and rights itself
Entering a connection string in the installer is now enough: the new
command `SQLInsight.Agent.exe setup-repository` runs as a deferred custom
action after the services exist and sqlinsight.ini is written, and before
they start. It runs as the installing user on purpose (SQL Server must see
a person with rights, not SYSTEM) and:

- creates the database from Database=... if it is missing
- creates logins/users for the service accounts: agent db_owner, web
  db_datareader. NT SERVICE\... accounts arrive at another computer as
  DOMAIN\COMPUTER$, which then gets the login instead
- runs the schema migrations
- with MONITOR_SERVER: creates the agent's login there and applies
  grant-least-privilege.sql (embedded). A failure there is a warning only

A repository failure stops the installation; the reason goes to the
Application event log with a Danish hint (login, rights, certificate,
unreachable). SKIP_REPOSITORY_SETUP=1 leaves it to a DBA. Safe to rerun.

verify-msi.py allows this one action to impersonate and checks its
placement, Return=check and condition. Tested against a local SQL
Express (twice, idempotent) and with an unreachable server. Version 1.0.1.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-01 08:18:26 +02:00

225 lines
16 KiB
XML

<?xml version="1.0" encoding="utf-8"?>
<!--
SQLInsight Pro: Agent and Web as Windows services.
Built with wixl (msitools) by installer/build-msi.sh; WiX 3 syntax, so WiX 3 on Windows can build it too.
Properties (msiexec /i SQLInsight-Pro.msi PROPERTY=value ... /qn):
REPOSITORY_CONNECTION Connection string to the repository database (required for the services to start)
MONITOR_SERVER Optional first SQL Server to monitor (name, or name\instance, or name,port)
AGENT_ACCOUNT Account for SQLInsight Agent. Default the virtual account NT SERVICE\SQLInsight Agent,
which reaches other servers as the computer account (DOMAIN\SERVER$). A gMSA: DOMAIN\svc-sqlinsight$
WEB_ACCOUNT Account for SQLInsight Web. Default NT SERVICE\SQLInsight Web
WEB_PORT Port for the web UI (default 5080)
SKIP_REPOSITORY_SETUP 1 = do not create the repository database, logins and rights (the admin does it, see
docs/installation.md). Otherwise the installing user's Windows account does it.
Features: Agent, Web (both by default) and UpdateTask under Agent (ADDLOCAL=ALL: installs downloaded updates hourly as SYSTEM).
An upgrade keeps the features that were installed.
-->
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
<Product Id="*" Name="SQLInsight Pro" Language="1033" Version="$(var.Version)" Manufacturer="SEOSoft"
UpgradeCode="91D05A67-4470-4C4F-9422-15CEF1B559BB">
<Package InstallerVersion="500" Compressed="yes" InstallScope="perMachine" Platform="x64"
Description="SQLInsight Pro $(var.Version)" Comments="SQL Server monitoring with deep expertise" Manufacturer="SEOSoft" />
<Media Id="1" Cabinet="sqlinsight.cab" EmbedCab="yes" CompressionLevel="high" />
<!-- Newer versions replace older ones in place; a downgrade is refused. -->
<Upgrade Id="91D05A67-4470-4C4F-9422-15CEF1B559BB">
<UpgradeVersion OnlyDetect="no" MigrateFeatures="yes" Property="OLDVERSIONFOUND" Minimum="0.0.0" IncludeMinimum="yes" Maximum="$(var.Version)" IncludeMaximum="no" />
<UpgradeVersion OnlyDetect="yes" Property="NEWERVERSIONFOUND" Minimum="$(var.Version)" IncludeMinimum="no" />
</Upgrade>
<Condition Message="A newer version of SQLInsight Pro is already installed.">NOT NEWERVERSIONFOUND</Condition>
<Condition Message="SQLInsight Pro requires 64-bit Windows Server 2016 / Windows 10 or later.">VersionNT64 &gt;= 603</Condition>
<Condition Message="Installing SQLInsight Pro requires administrator rights.">Privileged</Condition>
<Property Id="REPOSITORY_CONNECTION" Secure="yes" />
<Property Id="MONITOR_SERVER" Secure="yes" />
<Property Id="AGENT_ACCOUNT" Value="NT SERVICE\SQLInsight Agent" Secure="yes" />
<Property Id="WEB_ACCOUNT" Value="NT SERVICE\SQLInsight Web" Secure="yes" />
<Property Id="WEB_PORT" Value="5080" Secure="yes" />
<Property Id="SKIP_REPOSITORY_SETUP" Secure="yes" />
<!-- Set when the update task was installed by an earlier version, so an upgrade keeps it (see the UpdateTask feature). -->
<Property Id="UPDATETASK_INSTALLED">
<RegistrySearch Id="UpdateTaskInstalled" Root="HKLM" Key="SOFTWARE\SEOSoft\SQLInsight" Name="UpdateTask" Type="raw" Win64="yes" />
</Property>
<Property Id="ARPURLINFOABOUT" Value="https://git.seosoft.dk/seo/sqlinsight-pro" />
<Property Id="ARPHELPLINK" Value="https://git.seosoft.dk/seo/sqlinsight-pro/src/branch/main/docs" />
<Directory Id="TARGETDIR" Name="SourceDir">
<Directory Id="ProgramFiles64Folder">
<Directory Id="INSTALLFOLDER" Name="SQLInsight">
<Directory Id="AGENTDIR" Name="Agent" />
<Directory Id="WEBDIR" Name="Web" />
</Directory>
</Directory>
<Directory Id="CommonAppDataFolder">
<Directory Id="DATADIR" Name="SQLInsight">
<Directory Id="RULESDIR" Name="rules" />
</Directory>
</Directory>
</Directory>
<!-- Agent: service, settings from the installer, data folder, event log source. -->
<DirectoryRef Id="AGENTDIR">
<Component Id="AgentService" Guid="0B7E3E43-2C42-4F55-9A4C-6D1E0B6F2A01" Win64="yes">
<File Id="AgentExe" Source="$(var.AgentDir)/SQLInsight.Agent.exe" KeyPath="yes" />
<ServiceInstall Id="AgentServiceInstall" Name="SQLInsight Agent" DisplayName="SQLInsight Agent"
Description="Monitors SQL Server and stores findings in the SQLInsight repository."
Type="ownProcess" Start="auto" ErrorControl="normal" Account="[AGENT_ACCOUNT]" Vital="yes" />
<ServiceControl Id="AgentServiceStop" Name="SQLInsight Agent" Stop="both" Remove="uninstall" Wait="yes" />
</Component>
<Component Id="AgentSettings" Guid="0B7E3E43-2C42-4F55-9A4C-6D1E0B6F2A02" Win64="yes" NeverOverwrite="yes" Permanent="yes">
<File Id="AgentAppSettings" Source="$(var.AgentDir)/appsettings.json" KeyPath="yes" />
</Component>
<Component Id="AgentIni" Guid="0B7E3E43-2C42-4F55-9A4C-6D1E0B6F2A03" Win64="yes" Permanent="yes">
<Condition>REPOSITORY_CONNECTION</Condition>
<CreateFolder />
<IniFile Id="AgentIniRepository" Name="sqlinsight.ini" Directory="AGENTDIR" Action="addLine"
Section="SQLInsight:Repository" Key="ConnectionString" Value="[REPOSITORY_CONNECTION]" />
</Component>
<Component Id="AgentIniInstance" Guid="0B7E3E43-2C42-4F55-9A4C-6D1E0B6F2A04" Win64="yes" Permanent="yes">
<Condition>MONITOR_SERVER</Condition>
<CreateFolder />
<IniFile Id="AgentIniInstanceName" Name="sqlinsight.ini" Directory="AGENTDIR" Action="createLine"
Section="SQLInsight:Instances:0" Key="Name" Value="[MONITOR_SERVER]" />
<IniFile Id="AgentIniInstanceServer" Name="sqlinsight.ini" Directory="AGENTDIR" Action="createLine"
Section="SQLInsight:Instances:0" Key="Server" Value="[MONITOR_SERVER]" />
</Component>
<!-- Started only when there is something to connect to, so a plain install never fails on a missing setting. -->
<Component Id="AgentServiceStart" Guid="0B7E3E43-2C42-4F55-9A4C-6D1E0B6F2A05" Win64="yes">
<Condition>REPOSITORY_CONNECTION OR OLDVERSIONFOUND</Condition>
<CreateFolder />
<ServiceControl Id="AgentServiceStartControl" Name="SQLInsight Agent" Start="install" Wait="no" />
</Component>
<!-- Tells the updater that Windows Installer owns the files: new versions are then installed as MSI packages. -->
<Component Id="InstallInfo" Guid="0B7E3E43-2C42-4F55-9A4C-6D1E0B6F2A09" Win64="yes">
<RegistryKey Root="HKLM" Key="SOFTWARE\SEOSoft\SQLInsight">
<RegistryValue Name="Installer" Type="string" Value="msi" KeyPath="yes" />
<RegistryValue Name="Version" Type="string" Value="[ProductVersion]" />
</RegistryKey>
</Component>
<Component Id="EventSource" Guid="0B7E3E43-2C42-4F55-9A4C-6D1E0B6F2A06" Win64="yes">
<RegistryKey Root="HKLM" Key="SYSTEM\CurrentControlSet\Services\EventLog\Application\SQLInsight Pro">
<RegistryValue Name="EventMessageFile" Type="expandable" Value="%SystemRoot%\Microsoft.NET\Framework64\v4.0.30319\EventLogMessages.dll" KeyPath="yes" />
</RegistryKey>
</Component>
</DirectoryRef>
<DirectoryRef Id="INSTALLFOLDER">
<!-- Only here so the UpdateTask feature has something to install; the task itself is made by schtasks. -->
<Component Id="UpdateTaskMarker" Guid="0B7E3E43-2C42-4F55-9A4C-6D1E0B6F2A08" Win64="yes">
<RegistryValue Root="HKLM" Key="SOFTWARE\SEOSoft\SQLInsight" Name="UpdateTask" Type="integer" Value="1" KeyPath="yes" />
</Component>
</DirectoryRef>
<DirectoryRef Id="RULESDIR">
<Component Id="DataFolder" Guid="0B7E3E43-2C42-4F55-9A4C-6D1E0B6F2A07" Win64="yes" Permanent="yes">
<CreateFolder />
</Component>
</DirectoryRef>
<!-- Web: service on its own Kestrel port with Windows authentication. -->
<DirectoryRef Id="WEBDIR">
<Component Id="WebService" Guid="0B7E3E43-2C42-4F55-9A4C-6D1E0B6F2A11" Win64="yes">
<File Id="WebExe" Source="$(var.WebDir)/SQLInsight.Web.exe" KeyPath="yes" />
<ServiceInstall Id="WebServiceInstall" Name="SQLInsight Web" DisplayName="SQLInsight Web"
Description="Web interface for SQLInsight Pro."
Type="ownProcess" Start="auto" ErrorControl="normal" Account="[WEB_ACCOUNT]" Vital="yes" />
<ServiceControl Id="WebServiceStop" Name="SQLInsight Web" Stop="both" Remove="uninstall" Wait="yes" />
</Component>
<Component Id="WebSettings" Guid="0B7E3E43-2C42-4F55-9A4C-6D1E0B6F2A12" Win64="yes" NeverOverwrite="yes" Permanent="yes">
<File Id="WebAppSettings" Source="$(var.WebDir)/appsettings.json" KeyPath="yes" />
</Component>
<Component Id="WebIni" Guid="0B7E3E43-2C42-4F55-9A4C-6D1E0B6F2A13" Win64="yes" Permanent="yes">
<Condition>REPOSITORY_CONNECTION</Condition>
<CreateFolder />
<IniFile Id="WebIniRepository" Name="sqlinsight.ini" Directory="WEBDIR" Action="addLine"
Section="SQLInsight:Repository" Key="ConnectionString" Value="[REPOSITORY_CONNECTION]" />
<IniFile Id="WebIniUrl" Name="sqlinsight.ini" Directory="WEBDIR" Action="addLine"
Section="Kestrel:Endpoints:Http" Key="Url" Value="http://*:[WEB_PORT]" />
</Component>
<Component Id="WebServiceStart" Guid="0B7E3E43-2C42-4F55-9A4C-6D1E0B6F2A14" Win64="yes">
<Condition>REPOSITORY_CONNECTION OR OLDVERSIONFOUND</Condition>
<CreateFolder />
<ServiceControl Id="WebServiceStartControl" Name="SQLInsight Web" Start="install" Wait="no" />
</Component>
</DirectoryRef>
<!-- System tools run by the deferred custom actions below (their paths are set first, as properties). -->
<CustomAction Id="SetIcaclsPath" Property="ICACLS_EXE" Value="[System64Folder]icacls.exe" />
<CustomAction Id="SetNetshPath" Property="NETSH_EXE" Value="[System64Folder]netsh.exe" />
<CustomAction Id="SetSchtasksPath" Property="SCHTASKS_EXE" Value="[System64Folder]schtasks.exe" />
<!-- After CostFinalize, when AGENTDIR is resolved. -->
<CustomAction Id="SetAgentExePath" Property="AGENT_EXE" Value="[AGENTDIR]SQLInsight.Agent.exe" />
<!-- Repository: creates the database if it is missing, logins and users for both service accounts, the schema, and
the agent's least-privilege rights on MONITOR_SERVER. Runs as the INSTALLING USER (Impersonate="yes"), on
purpose: SQL Server must see a person with rights there (sysadmin, or dbcreator + securityadmin), not SYSTEM.
After InstallServices, because NT SERVICE\... accounts only exist once the services do, and after
WriteIniValues, because it reads the connection string from sqlinsight.ini. A failure stops the installation
(details in the Application event log, source SQLInsight Pro); SKIP_REPOSITORY_SETUP=1 leaves it to the admin. -->
<CustomAction Id="SetupRepository" Property="AGENT_EXE" Execute="deferred" Impersonate="yes" Return="check"
ExeCommand="setup-repository --agent-account &quot;[AGENT_ACCOUNT]&quot; --web-account &quot;[WEB_ACCOUNT]&quot; --grant-monitored" />
<!-- The data folder: only SYSTEM, Administrators and the agent's account; nobody else may drop rules or updates there. -->
<CustomAction Id="SetDataFolderAcl" Property="ICACLS_EXE" Execute="deferred" Impersonate="no" Return="ignore"
ExeCommand="&quot;[DATADIR].&quot; /inheritance:r /grant:r *S-1-5-18:(OI)(CI)F *S-1-5-32-544:(OI)(CI)F &quot;[AGENT_ACCOUNT]:(OI)(CI)M&quot;" />
<!-- Windows Firewall: open the web port while Web is installed. -->
<CustomAction Id="OpenFirewall" Property="NETSH_EXE" Execute="deferred" Impersonate="no" Return="ignore"
ExeCommand="advfirewall firewall add rule name=&quot;SQLInsight Web&quot; dir=in action=allow protocol=TCP localport=[WEB_PORT]" />
<CustomAction Id="ReplaceFirewallRule" Property="NETSH_EXE" Execute="deferred" Impersonate="no" Return="ignore"
ExeCommand="advfirewall firewall delete rule name=&quot;SQLInsight Web&quot;" />
<CustomAction Id="CloseFirewall" Property="NETSH_EXE" Execute="deferred" Impersonate="no" Return="ignore"
ExeCommand="advfirewall firewall delete rule name=&quot;SQLInsight Web&quot;" />
<!-- Update task: installs updates the agent has downloaded and verified, hourly, as SYSTEM (see docs/opdatering.md). -->
<CustomAction Id="CreateUpdateTask" Property="SCHTASKS_EXE" Execute="deferred" Impersonate="no" Return="ignore"
ExeCommand="/Create /F /TN &quot;SQLInsight Update&quot; /SC HOURLY /RU SYSTEM /RL HIGHEST /TR &quot;\&quot;[AGENTDIR]SQLInsight.Agent.exe\&quot; apply-update&quot;" />
<CustomAction Id="DeleteUpdateTask" Property="SCHTASKS_EXE" Execute="deferred" Impersonate="no" Return="ignore"
ExeCommand="/Delete /F /TN &quot;SQLInsight Update&quot;" />
<InstallExecuteSequence>
<RemoveExistingProducts After="InstallExecute" />
<Custom Action="SetIcaclsPath" Before="LaunchConditions">1</Custom>
<Custom Action="SetNetshPath" After="SetIcaclsPath">1</Custom>
<Custom Action="SetSchtasksPath" After="SetNetshPath">1</Custom>
<Custom Action="SetAgentExePath" After="CostFinalize">1</Custom>
<!-- Removal: not when an upgrade removes the old version, or it would undo what the new one just set up. -->
<Custom Action="DeleteUpdateTask" Sequence="3400">&amp;UpdateTask=2 AND NOT UPGRADINGPRODUCTCODE</Custom>
<Custom Action="CloseFirewall" Sequence="3401">&amp;Web=2 AND NOT UPGRADINGPRODUCTCODE</Custom>
<!-- After InstallServices: the virtual account NT SERVICE\SQLInsight Agent only exists once the service does. -->
<Custom Action="SetDataFolderAcl" Sequence="5804">&amp;Agent=3</Custom>
<Custom Action="CreateUpdateTask" Sequence="5801">&amp;UpdateTask=3</Custom>
<Custom Action="ReplaceFirewallRule" Sequence="5802">&amp;Web=3</Custom>
<Custom Action="OpenFirewall" Sequence="5803">&amp;Web=3</Custom>
<!-- Before StartServices (5900), so the services find their database. -->
<Custom Action="SetupRepository" Sequence="5805">&amp;Agent=3 AND REPOSITORY_CONNECTION AND NOT SKIP_REPOSITORY_SETUP</Custom>
</InstallExecuteSequence>
<Feature Id="Agent" Title="SQLInsight Agent" Description="Monitors the SQL Server instances." Level="1" Absent="allow">
<ComponentRef Id="AgentService" />
<ComponentRef Id="AgentSettings" />
<ComponentRef Id="AgentIni" />
<ComponentRef Id="AgentIniInstance" />
<ComponentRef Id="AgentServiceStart" />
<ComponentRef Id="EventSource" />
<ComponentRef Id="InstallInfo" />
<ComponentRef Id="DataFolder" />
<ComponentGroupRef Id="AgentFiles" />
<Feature Id="UpdateTask" Title="Automatic updates" Description="Scheduled task that installs downloaded, verified updates." Level="1000" Absent="allow">
<ComponentRef Id="UpdateTaskMarker" />
<Condition Level="1">UPDATETASK_INSTALLED</Condition>
</Feature>
</Feature>
<Feature Id="Web" Title="SQLInsight Web" Description="The web interface (Windows authentication)." Level="1" Absent="allow">
<ComponentRef Id="WebService" />
<ComponentRef Id="WebSettings" />
<ComponentRef Id="WebIni" />
<ComponentRef Id="WebServiceStart" />
<ComponentGroupRef Id="WebFiles" />
</Feature>
<Icon Id="SQLInsight.ico" SourceFile="$(var.InstallerDir)/sqlinsight.ico" />
<Property Id="ARPPRODUCTICON" Value="SQLInsight.ico" />
<UIRef Id="SQLInsightUI" />
<WixVariable Id="WixUILicenseRtf" Value="$(var.InstallerDir)/License.rtf" />
</Product>
</Wix>