mirror of
https://github.com/fleetdm/fleet
synced 2026-05-24 09:28:54 +00:00
Windows installer now ensures that legacy osquery installations gets removed during clean install (#9048)
This relates to #8891. This PR introduces Wix custom actions usage.
This commit is contained in:
parent
e3a626a00d
commit
605ae861c9
4 changed files with 485 additions and 5 deletions
2
.github/workflows/fleet-and-orbit.yml
vendored
2
.github/workflows/fleet-and-orbit.yml
vendored
|
|
@ -406,7 +406,7 @@ jobs:
|
|||
- name: Install msi
|
||||
shell: pwsh
|
||||
run: |
|
||||
msiexec /i ${{ steps.download.outputs.download-path }}\fleet-osquery.msi /quiet /passive /lv log.txt
|
||||
Start-Process msiexec -ArgumentList "/i ${{ steps.download.outputs.download-path }}\fleet-osquery.msi /quiet /passive /lv log.txt" -Wait
|
||||
|
||||
- name: Wait enroll
|
||||
run: |
|
||||
|
|
|
|||
1
changes/bug-8891-uninstall-legacy-osquery-installations
Normal file
1
changes/bug-8891-uninstall-legacy-osquery-installations
Normal file
|
|
@ -0,0 +1 @@
|
|||
* Windows installer now ensures that the installed osquery version gets removed before installing Orbit
|
||||
|
|
@ -91,6 +91,10 @@ func BuildMSI(opt Options) (string, error) {
|
|||
return "", fmt.Errorf("write eventlog file: %w", err)
|
||||
}
|
||||
|
||||
if err := writePowershellInstallerUtilsFile(opt, orbitRoot); err != nil {
|
||||
return "", fmt.Errorf("write powershell installer utils file: %w", err)
|
||||
}
|
||||
|
||||
if err := writeWixFile(opt, tmpDir); err != nil {
|
||||
return "", fmt.Errorf("write wix file: %w", err)
|
||||
}
|
||||
|
|
@ -174,3 +178,22 @@ func writeEventLogFile(opt Options, rootPath string) error {
|
|||
|
||||
return nil
|
||||
}
|
||||
|
||||
func writePowershellInstallerUtilsFile(opt Options, rootPath string) error {
|
||||
// Powershell installer utils file is going to be built and dumped into working directory
|
||||
path := filepath.Join(rootPath, "installer_utils.ps1")
|
||||
if err := secure.MkdirAll(filepath.Dir(path), constant.DefaultDirMode); err != nil {
|
||||
return fmt.Errorf("powershell installer utils location creation: %w", err)
|
||||
}
|
||||
|
||||
var contents bytes.Buffer
|
||||
if err := windowsPSInstallerUtils.Execute(&contents, opt); err != nil {
|
||||
return fmt.Errorf("powershell installer utils transform: %w", err)
|
||||
}
|
||||
|
||||
if err := ioutil.WriteFile(path, contents.Bytes(), constant.DefaultFileMode); err != nil {
|
||||
return fmt.Errorf("powershell installer utils file write: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,6 +35,14 @@ var windowsWixTemplate = template.Must(template.New("").Option("missingkey=error
|
|||
|
||||
<MediaTemplate EmbedCab="yes" />
|
||||
|
||||
<Property Id="POWERSHELLEXE">
|
||||
<RegistrySearch Id="POWERSHELLEXE"
|
||||
Type="raw"
|
||||
Root="HKLM"
|
||||
Key="SOFTWARE\Microsoft\PowerShell\1\ShellIds\Microsoft.PowerShell"
|
||||
Name="Path" />
|
||||
</Property>
|
||||
|
||||
<MajorUpgrade AllowDowngrades="yes" />
|
||||
|
||||
<Directory Id="TARGETDIR" Name="SourceDir">
|
||||
|
|
@ -95,12 +103,33 @@ var windowsWixTemplate = template.Must(template.New("").Option("missingkey=error
|
|||
</Directory>
|
||||
</Directory>
|
||||
|
||||
<CustomAction Id="StopOrbit_cmd" Property="WixQuietExecCmdLine" Value='"[WindowsFolder]System32\taskkill.exe" /F /IM osqueryd.exe /IM fleet-desktop.exe /IM orbit.exe' />
|
||||
<CustomAction Id="StopOrbit" BinaryKey="WixCA" DllEntry="WixQuietExec" Execute="immediate" Return="check"/>
|
||||
<SetProperty Id="CA_UninstallOsquery"
|
||||
Before ="CA_UninstallOsquery"
|
||||
Sequence="execute"
|
||||
Value='"[POWERSHELLEXE]" -NoLogo -NonInteractive -NoProfile -ExecutionPolicy Bypass -File "[ORBITROOT]installer_utils.ps1" -uninstallOsquery' />
|
||||
|
||||
<CustomAction Id="CA_UninstallOsquery"
|
||||
BinaryKey="WixCA"
|
||||
DllEntry="WixQuietExec64"
|
||||
Execute="deferred"
|
||||
Return="check"
|
||||
Impersonate="no" />
|
||||
|
||||
<SetProperty Id="CA_StopOrbit"
|
||||
Before ="CA_StopOrbit"
|
||||
Sequence="execute"
|
||||
Value='"[POWERSHELLEXE]" -NoLogo -NonInteractive -NoProfile -ExecutionPolicy Bypass -File "[ORBITROOT]installer_utils.ps1" -stopOrbit' />
|
||||
|
||||
<CustomAction Id="CA_StopOrbit"
|
||||
BinaryKey="WixCA"
|
||||
DllEntry="WixQuietExec64"
|
||||
Execute="deferred"
|
||||
Return="check"
|
||||
Impersonate="no" />
|
||||
|
||||
<InstallExecuteSequence>
|
||||
<Custom Action='StopOrbit_cmd' Before='RemoveFiles'>(NOT UPGRADINGPRODUCTCODE) AND (REMOVE="ALL")</Custom>
|
||||
<Custom Action='StopOrbit' After='StopOrbit_cmd'>(NOT UPGRADINGPRODUCTCODE) AND (REMOVE="ALL")</Custom>
|
||||
<Custom Action='CA_StopOrbit' Before='RemoveFiles'>(NOT UPGRADINGPRODUCTCODE) AND (REMOVE="ALL")</Custom> <!-- Only happens during uninstall -->
|
||||
<Custom Action='CA_UninstallOsquery' After='InstallFiles'>NOT Installed AND NOT WIX_UPGRADE_DETECTED</Custom> <!-- Only happens during first install -->
|
||||
</InstallExecuteSequence>
|
||||
|
||||
<Feature Id="Orbit" Title="Fleet osquery" Level="1" Display="hidden">
|
||||
|
|
@ -172,3 +201,430 @@ var windowsOsqueryEventLogTemplate = template.Must(template.New("").Option("miss
|
|||
</localization>
|
||||
</instrumentationManifest>
|
||||
`))
|
||||
|
||||
var windowsPSInstallerUtils = template.Must(template.New("").Option("missingkey=error").Parse(
|
||||
`param(
|
||||
[switch] $uninstallOsquery = $false,
|
||||
[switch] $uninstallOrbit = $false,
|
||||
[switch] $stopOrbit = $false,
|
||||
[switch] $help = $false
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "SilentlyContinue"
|
||||
|
||||
$code = @"
|
||||
using Microsoft.Win32;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
public class RegistryUtils
|
||||
{
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
internal static extern IntPtr GetCurrentProcess();
|
||||
|
||||
[DllImport("advapi32.dll", ExactSpelling = true, SetLastError = true)]
|
||||
internal static extern bool AdjustTokenPrivileges(IntPtr htok, bool disall,
|
||||
ref TokPriv1Luid newst, int len, IntPtr prev, IntPtr relen);
|
||||
|
||||
[DllImport("advapi32.dll", ExactSpelling = true, SetLastError = true)]
|
||||
internal static extern bool OpenProcessToken(IntPtr h, int acc, ref IntPtr phtok);
|
||||
|
||||
[DllImport("advapi32.dll", SetLastError = true)]
|
||||
internal static extern bool LookupPrivilegeValue(string host, string name, ref long pluid);
|
||||
|
||||
[DllImport("advapi32.dll", SetLastError = true)]
|
||||
static extern int RegLoadKey(UInt32 hKey, String lpSubKey, String lpFile);
|
||||
|
||||
[DllImport("advapi32.dll", SetLastError = true)]
|
||||
static extern int RegUnLoadKey(UInt32 hKey, string lpSubKey);
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
internal struct TokPriv1Luid
|
||||
{
|
||||
public int Count;
|
||||
public long Luid;
|
||||
public int Attr;
|
||||
}
|
||||
|
||||
internal const int SE_PRIVILEGE_ENABLED = 0x00000002;
|
||||
internal const int SE_PRIVILEGE_DISABLED = 0x00000000;
|
||||
internal const int TOKEN_QUERY = 0x00000008;
|
||||
internal const int TOKEN_ADJUST_PRIVILEGES = 0x00000020;
|
||||
|
||||
public static bool EnablePrivilege(string privilege, bool disable)
|
||||
{
|
||||
TokPriv1Luid tp;
|
||||
IntPtr htok = IntPtr.Zero;
|
||||
|
||||
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, ref htok))
|
||||
{
|
||||
Console.WriteLine("EnablePrivilege - Failed to obtain handle to primary access token");
|
||||
return false;
|
||||
}
|
||||
|
||||
tp.Count = 1;
|
||||
tp.Luid = 0;
|
||||
|
||||
if (disable)
|
||||
{
|
||||
tp.Attr = SE_PRIVILEGE_DISABLED;
|
||||
}
|
||||
else
|
||||
{
|
||||
tp.Attr = SE_PRIVILEGE_ENABLED;
|
||||
}
|
||||
|
||||
if (!LookupPrivilegeValue(null, privilege, ref tp.Luid))
|
||||
{
|
||||
Console.WriteLine("EnablePrivilege - Failed to lookup privilege {0}", privilege);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!AdjustTokenPrivileges(htok, false, ref tp, 0, IntPtr.Zero, IntPtr.Zero))
|
||||
{
|
||||
Console.WriteLine("EnablePrivilege - Failed to modify privilege {0}", privilege);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
public static void LoadUsersHives()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (EnablePrivilege("SeRestorePrivilege", false) && EnablePrivilege("SeBackupPrivilege", false))
|
||||
{
|
||||
string usersRoot = System.Environment.GetEnvironmentVariable("SystemDrive") + "\\users\\";
|
||||
string[] userDir = System.IO.Directory.GetDirectories(usersRoot, "*", System.IO.SearchOption.TopDirectoryOnly);
|
||||
|
||||
foreach (string fullDirectoryName in userDir)
|
||||
{
|
||||
string userDirName = new DirectoryInfo(fullDirectoryName).Name;
|
||||
if (userDirName.Length == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
string targetFile = fullDirectoryName + "\\NTUSER.DAT";
|
||||
if (!File.Exists(targetFile))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
const uint HKEY_USERS = 0x80000003;
|
||||
int retRegLoadKey = RegLoadKey(HKEY_USERS, userDirName, targetFile);
|
||||
Console.WriteLine("RegLoadKey result was {0}", retRegLoadKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public static void UnloadUsersHives()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (EnablePrivilege("SeRestorePrivilege", false) && EnablePrivilege("SeBackupPrivilege", false))
|
||||
{
|
||||
string usersRoot = System.Environment.GetEnvironmentVariable("SystemDrive") + "\\users\\";
|
||||
string[] userDir = System.IO.Directory.GetDirectories(usersRoot, "*", System.IO.SearchOption.TopDirectoryOnly);
|
||||
|
||||
foreach (string fullDirectoryName in userDir)
|
||||
{
|
||||
var userDirName = new DirectoryInfo(fullDirectoryName).Name;
|
||||
if (userDirName.Length == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
const uint HKEY_USERS = 0x80000003;
|
||||
int retRegUnLoadKey = RegUnLoadKey(HKEY_USERS, userDirName);
|
||||
Console.WriteLine("RegUnLoadKey result was {0}", retRegUnLoadKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public static void RemoveOsqueryInstallationFromUserHives()
|
||||
{
|
||||
try
|
||||
{
|
||||
LoadUsersHives();
|
||||
|
||||
foreach (var username in Registry.Users.GetSubKeyNames())
|
||||
{
|
||||
string targetKey = username + "\\SOFTWARE\\Microsoft\\Installer\\Products\\";
|
||||
RegistryKey rootKey = Registry.Users.OpenSubKey(targetKey, writable: true);
|
||||
|
||||
if (rootKey != null)
|
||||
{
|
||||
foreach (var productEntry in rootKey.GetSubKeyNames())
|
||||
{
|
||||
RegistryKey productKey = rootKey.OpenSubKey(productEntry);
|
||||
if (productKey != null)
|
||||
{
|
||||
if ((string)productKey.GetValue("ProductName") == "osquery")
|
||||
{
|
||||
productKey.Close();
|
||||
rootKey.DeleteSubKeyTree(productEntry);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
UnloadUsersHives();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine("There was an exception when removing osquery installer from user hives: {0}", ex.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
"@
|
||||
|
||||
Add-Type -TypeDefinition $code -Language CSharp
|
||||
|
||||
function Test-Administrator
|
||||
{
|
||||
[OutputType([bool])]
|
||||
param()
|
||||
process {
|
||||
[Security.Principal.WindowsPrincipal]$user = [Security.Principal.WindowsIdentity]::GetCurrent();
|
||||
return $user.IsInRole([Security.Principal.WindowsBuiltinRole]::Administrator);
|
||||
}
|
||||
}
|
||||
|
||||
function Do-Help {
|
||||
$programName = (Get-Item $PSCommandPath ).Name
|
||||
|
||||
Write-Host "Usage: $programName (-uninstallOsquery|-uninstallOrbit|-stopOrbit|-help)" -foregroundcolor Yellow
|
||||
Write-Host ""
|
||||
Write-Host " Only one of the following options can be used. Using multiple will result in "
|
||||
Write-Host " options being ignored."
|
||||
Write-Host " -uninstallOsquery Uninstall Osquery"
|
||||
Write-Host " -uninstallOrbit Uninstall Orbit"
|
||||
Write-Host " -stopOrbit Stop Orbit"
|
||||
Write-Host ""
|
||||
Write-Host " -help Shows this help screen"
|
||||
|
||||
Exit 1
|
||||
}
|
||||
|
||||
#Stops Osquery service and related processes
|
||||
function Stop-Osquery {
|
||||
|
||||
$kServiceName = "osqueryd"
|
||||
|
||||
# Stop Service
|
||||
Stop-Service -Name $kServiceName
|
||||
Start-Sleep -Milliseconds 1000
|
||||
|
||||
# Ensure that no process left running
|
||||
Get-Process -Name $kServiceName | Stop-Process -Force
|
||||
}
|
||||
|
||||
#Stops Orbit service and related processes
|
||||
function Stop-Orbit {
|
||||
|
||||
# Stop Service
|
||||
Stop-Service -Name "Fleet osquery"
|
||||
Start-Sleep -Milliseconds 1000
|
||||
|
||||
# Ensure that no process left running
|
||||
Get-Process -Name "orbit" | Stop-Process -Force
|
||||
Get-Process -Name "osqueryd" | Stop-Process -Force
|
||||
Get-Process -Name "fleet-desktop" | Stop-Process -Force
|
||||
Start-Sleep -Milliseconds 1000
|
||||
}
|
||||
|
||||
#Revove Orbit footprint from registry and disk
|
||||
function Force-Remove-Orbit {
|
||||
|
||||
#Stoping Orbit
|
||||
Stop-Orbit
|
||||
|
||||
#Remove Service
|
||||
$service = Get-WmiObject -Class Win32_Service -Filter "Name='Fleet osquery'"
|
||||
$service.delete()
|
||||
|
||||
#Removing Program files entries
|
||||
$targetPath = $Env:Programfiles + "\\Orbit"
|
||||
Remove-Item -LiteralPath $targetPath -Force -Recurse
|
||||
|
||||
#Remove HKLM registry entries
|
||||
Get-ChildItem "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall" -Recurse | Where-Object {($_.ValueCount -gt 0)} | ForEach-Object {
|
||||
|
||||
# Filter for osquery entries
|
||||
$properties = Get-ItemProperty $_.PSPath | Where-Object {($_.DisplayName -eq "Fleet osquery")}
|
||||
if ($properties) {
|
||||
|
||||
#Remove Registry Entries
|
||||
$regKey = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\" + $_.PSChildName
|
||||
Get-Item $regKey | Remove-Item -Force
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#Revove Osquery footprint from registry and disk
|
||||
function Force-Remove-Osquery {
|
||||
|
||||
#Stoping Osquery
|
||||
Stop-Osquery
|
||||
|
||||
#Remove Service
|
||||
$service = Get-WmiObject -Class Win32_Service -Filter "Name='osqueryd'"
|
||||
$service.delete() | Out-Null
|
||||
|
||||
#Remove HKLM registry entries and disk footprint
|
||||
Get-ChildItem "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall" -Recurse | Where-Object {($_.ValueCount -gt 0)} | ForEach-Object {
|
||||
|
||||
# Filter for osquery entries
|
||||
$properties = Get-ItemProperty $_.PSPath | Where-Object {($_.DisplayName -eq "osquery")}
|
||||
if ($properties) {
|
||||
|
||||
#Remove files from osquery location
|
||||
if ($properties.InstallLocation){
|
||||
Remove-Item -LiteralPath $properties.InstallLocation -Force -Recurse
|
||||
}
|
||||
|
||||
#Remove Registry Entries
|
||||
$regKey = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\" + $_.PSChildName
|
||||
Get-Item $regKey | Remove-Item -Force
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
#Remove user entries if present
|
||||
[RegistryUtils]::RemoveOsqueryInstallationFromUserHives()
|
||||
}
|
||||
|
||||
function Graceful-Product-Uninstall($productName) {
|
||||
|
||||
if (!$productName) {
|
||||
Write-Host "Product name should be provided" -foregroundcolor Yellow
|
||||
return $false
|
||||
}
|
||||
|
||||
# Grabbing the location of msiexec.exe
|
||||
$targetBinPath = Resolve-Path "$env:windir\system32\msiexec.exe"
|
||||
if (!(Test-Path $targetBinPath)) {
|
||||
Write-Host "msiexec.exe cannot be located." -foregroundcolor Yellow
|
||||
return $false
|
||||
}
|
||||
|
||||
# Creating a COM instance of the WindowsInstaller.Installer COM object
|
||||
$Installer = New-Object -ComObject WindowsInstaller.Installer
|
||||
if (!$Installer) {
|
||||
Write-Host "There was a problem retrieving the installed packages." -foregroundcolor Yellow
|
||||
return $false
|
||||
}
|
||||
|
||||
# Enumerating the installed packages
|
||||
$ProductEnumFlag = 7 #installed packaged enumeration flag
|
||||
$InstallerProducts = $Installer.ProductsEx("", "", $ProductEnumFlag);
|
||||
if (!$InstallerProducts) {
|
||||
Write-Host "Installed packages cannot be retrieved." -foregroundcolor Yellow
|
||||
return $false
|
||||
}
|
||||
|
||||
# Iterating over the installed packages results and checking for osquery package
|
||||
ForEach ($Product in $InstallerProducts) {
|
||||
|
||||
$ProductCode = $null
|
||||
$VersionString = $null
|
||||
$ProductPath = $null
|
||||
|
||||
try {
|
||||
$ProductCode = $Product.ProductCode()
|
||||
$VersionString = $Product.InstallProperty("VersionString")
|
||||
$ProductPath = $Product.InstallProperty("ProductName")
|
||||
}
|
||||
catch { }
|
||||
|
||||
if ($ProductPath -like $productName) {
|
||||
Write-Host "Graceful uninstall of $ProductPath version $VersionString." -foregroundcolor Cyan
|
||||
$InstallProcess = Start-Process $targetBinPath -ArgumentList "/quiet /x $ProductCode" -PassThru -Verb RunAs -Wait
|
||||
if ($InstallProcess.ExitCode -eq 0) {
|
||||
return $true
|
||||
} else {
|
||||
Write-Host "There was an error uninstalling osquery. Error code was: $($InstallProcess.ExitCode)." -foregroundcolor Yellow
|
||||
return $false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $false
|
||||
}
|
||||
|
||||
|
||||
function Main {
|
||||
# Is Administrator check
|
||||
if (-not (Test-Administrator)) {
|
||||
Write-Host "Please run this script with Admin privileges!" -foregroundcolor Red
|
||||
Exit -1
|
||||
}
|
||||
|
||||
# Help commands
|
||||
if ($help) {
|
||||
Do-Help
|
||||
Exit -1
|
||||
}
|
||||
|
||||
if ($uninstallOsquery) {
|
||||
Write-Host "About to uninstall Osquery." -foregroundcolor Yellow
|
||||
|
||||
Stop-Osquery
|
||||
|
||||
if (Graceful-Product-Uninstall("osquery")) {
|
||||
Write-Host "Osquery was gracefully uninstalled." -foregroundcolor Cyan
|
||||
} else {
|
||||
Force-Remove-Osquery
|
||||
Write-Host "Osquery was uninstalled." -foregroundcolor Cyan
|
||||
}
|
||||
Exit 0
|
||||
|
||||
} elseif ($uninstallOrbit) {
|
||||
Write-Host "About to uninstall Orbit." -foregroundcolor Yellow
|
||||
|
||||
Stop-Orbit
|
||||
|
||||
if (Graceful-Product-Uninstall("Fleet osquery")) {
|
||||
Force-Remove-Orbit
|
||||
Write-Host "Orbit was gracefully uninstalled." -foregroundcolor Cyan
|
||||
} else {
|
||||
Force-Remove-Orbit
|
||||
Write-Host "Orbit was uninstalled." -foregroundcolor Cyan
|
||||
}
|
||||
Exit 0
|
||||
} elseif ($stopOrbit) {
|
||||
Write-Host "About to stop Orbit and remove it from system." -foregroundcolor Yellow
|
||||
|
||||
Stop-Orbit
|
||||
|
||||
Write-Host "Orbit was stopped." -foregroundcolor Cyan
|
||||
Exit 0
|
||||
} else {
|
||||
Write-Host "Invalid option selected: please see -help for usage details." -foregroundcolor Red
|
||||
Do-Help
|
||||
Exit -1
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
$null = Main
|
||||
`))
|
||||
|
|
|
|||
Loading…
Reference in a new issue