UI – Implement changes for package uninstall scripts in the add software modal (#21828)
## Addresses #21564 – see issue for task list     - [x] Manual QA for all new/changed functionality --------- Co-authored-by: Jacob Shandling <jacob@fleetdm.com>
This commit is contained in:
co-authored by
Jacob Shandling
parent
25d08d1051
commit
0cfbdc6f58
@@ -13,6 +13,7 @@ export interface IRevealButtonProps {
|
||||
autofocus?: boolean;
|
||||
disabled?: boolean;
|
||||
tooltipContent?: React.ReactNode;
|
||||
disabledTooltipContent?: React.ReactNode;
|
||||
onClick?:
|
||||
| ((value?: any) => void)
|
||||
| ((evt: React.MouseEvent<HTMLButtonElement>) => void);
|
||||
@@ -29,6 +30,7 @@ const RevealButton = ({
|
||||
autofocus,
|
||||
disabled,
|
||||
tooltipContent,
|
||||
disabledTooltipContent,
|
||||
onClick,
|
||||
}: IRevealButtonProps): JSX.Element => {
|
||||
const classNames = classnames(baseClass, className);
|
||||
@@ -36,11 +38,12 @@ const RevealButton = ({
|
||||
const buttonContent = () => {
|
||||
const text = isShowing ? hideText : showText;
|
||||
|
||||
const buttonText = tooltipContent ? (
|
||||
<TooltipWrapper tipContent={tooltipContent}>{text}</TooltipWrapper>
|
||||
) : (
|
||||
text
|
||||
);
|
||||
const buttonText =
|
||||
tooltipContent && !disabled ? (
|
||||
<TooltipWrapper tipContent={tooltipContent}>{text}</TooltipWrapper>
|
||||
) : (
|
||||
text
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -61,7 +64,7 @@ const RevealButton = ({
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
const button = (
|
||||
<Button
|
||||
variant="text-icon"
|
||||
className={classNames}
|
||||
@@ -72,6 +75,22 @@ const RevealButton = ({
|
||||
{buttonContent()}
|
||||
</Button>
|
||||
);
|
||||
|
||||
if (disabled && disabledTooltipContent) {
|
||||
// wrap the tooltip around the Button so it works while disabled
|
||||
return (
|
||||
<TooltipWrapper
|
||||
tipContent={disabledTooltipContent}
|
||||
showArrow
|
||||
underline={false}
|
||||
position="right"
|
||||
tipOffset={12}
|
||||
>
|
||||
{button}
|
||||
</TooltipWrapper>
|
||||
);
|
||||
}
|
||||
return button;
|
||||
};
|
||||
|
||||
export default RevealButton;
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
const unixPackageTypes = ["pkg", "deb"] as const;
|
||||
const windowsPackageTypes = ["msi", "exe"] as const;
|
||||
export const packageTypes = [
|
||||
...unixPackageTypes,
|
||||
...windowsPackageTypes,
|
||||
] as const;
|
||||
|
||||
export type WindowsPackageType = typeof windowsPackageTypes[number];
|
||||
export type UnixPackageType = typeof unixPackageTypes[number];
|
||||
export type PackageType = WindowsPackageType | UnixPackageType;
|
||||
|
||||
export const isWindowsPackageType = (s: any): s is WindowsPackageType => {
|
||||
return windowsPackageTypes.includes(s);
|
||||
};
|
||||
|
||||
export const isUnixPackageType = (s: any): s is UnixPackageType => {
|
||||
return unixPackageTypes.includes(s);
|
||||
};
|
||||
|
||||
export const isPackageType = (s: any): s is PackageType => {
|
||||
return packageTypes.includes(s);
|
||||
};
|
||||
+193
-56
@@ -1,33 +1,219 @@
|
||||
import React, { useState } from "react";
|
||||
|
||||
import { LEARN_MORE_ABOUT_BASE_LINK } from "utilities/constants";
|
||||
|
||||
import {
|
||||
isPackageType,
|
||||
isWindowsPackageType,
|
||||
PackageType,
|
||||
WindowsPackageType,
|
||||
} from "interfaces/package_type";
|
||||
|
||||
import Editor from "components/Editor";
|
||||
import CustomLink from "components/CustomLink";
|
||||
import FleetAce from "components/FleetAce";
|
||||
import RevealButton from "components/buttons/RevealButton";
|
||||
import { IAddPackageFormData } from "../AddPackageForm/AddPackageForm";
|
||||
|
||||
const unixInstallHelpText = (
|
||||
<>
|
||||
Use the $INSTALLER_PATH to point to the installer. Shell scripts are
|
||||
supported.{" "}
|
||||
<CustomLink
|
||||
url={`${LEARN_MORE_ABOUT_BASE_LINK}/install-scripts`}
|
||||
text="Learn more about install scripts"
|
||||
newTab
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
||||
const getWindowsInstallHelpText = (pkgType: WindowsPackageType) => (
|
||||
<>
|
||||
Use the $INSTALLER_PATH to point to the installer. PowerShell scripts are
|
||||
supported.{" "}
|
||||
<CustomLink
|
||||
url={`${LEARN_MORE_ABOUT_BASE_LINK}/${
|
||||
pkgType === "exe" ? "exe-" : ""
|
||||
}install-scripts`}
|
||||
text="Learn more about install scripts"
|
||||
newTab
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
||||
const unixPostInstallHelpText = "Shell scripts are supported.";
|
||||
const windowsPostInstallHelpText = "PowerShell scripts are supported.";
|
||||
|
||||
const PKG_TYPE_TO_ID_TEXT = {
|
||||
pkg: "package IDs",
|
||||
deb: "package name",
|
||||
msi: "product code",
|
||||
exe: "software name",
|
||||
} as const;
|
||||
|
||||
const getUninstallHelpText = (type: PackageType) => {
|
||||
return (
|
||||
<>
|
||||
$PACKAGE_ID will be populated with the {PKG_TYPE_TO_ID_TEXT[type]} from
|
||||
the .{type} file after the software is added.{" "}
|
||||
{isWindowsPackageType(type) && "Power"}
|
||||
Shell scripts are supported.{" "}
|
||||
<CustomLink
|
||||
url={`${LEARN_MORE_ABOUT_BASE_LINK}/uninstall-scripts`}
|
||||
text="Learn more about uninstall scripts"
|
||||
newTab
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const PACKAGE_TYPES_TO_HELP_TEXT: Record<
|
||||
PackageType,
|
||||
Record<string, Record<string, React.ReactNode>>
|
||||
> = {
|
||||
pkg: {
|
||||
install: {
|
||||
helpText: unixInstallHelpText,
|
||||
},
|
||||
postInstall: {
|
||||
helpText: unixPostInstallHelpText,
|
||||
},
|
||||
uninstall: {
|
||||
helpText: getUninstallHelpText("pkg"),
|
||||
},
|
||||
},
|
||||
deb: {
|
||||
install: {
|
||||
helpText: unixInstallHelpText,
|
||||
},
|
||||
postInstall: {
|
||||
helpText: unixPostInstallHelpText,
|
||||
},
|
||||
uninstall: {
|
||||
helpText: getUninstallHelpText("deb"),
|
||||
},
|
||||
},
|
||||
msi: {
|
||||
install: {
|
||||
helpText: getWindowsInstallHelpText("msi"),
|
||||
},
|
||||
postInstall: {
|
||||
helpText: windowsPostInstallHelpText,
|
||||
},
|
||||
uninstall: {
|
||||
helpText: getUninstallHelpText("msi"),
|
||||
},
|
||||
},
|
||||
exe: {
|
||||
install: {
|
||||
helpText: getWindowsInstallHelpText("exe"),
|
||||
},
|
||||
postInstall: {
|
||||
helpText: windowsPostInstallHelpText,
|
||||
},
|
||||
uninstall: {
|
||||
helpText: getUninstallHelpText("exe"),
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
const baseClass = "add-package-advanced-options";
|
||||
|
||||
interface IAddPackageAdvancedOptionsProps {
|
||||
errors: { preInstallQuery?: string; postInstallScript?: string };
|
||||
selectedPackage: IAddPackageFormData["software"];
|
||||
preInstallQuery?: string;
|
||||
installScript: string;
|
||||
postInstallScript?: string;
|
||||
uninstallScript?: string;
|
||||
onChangePreInstallQuery: (value?: string) => void;
|
||||
onChangeInstallScript: (value: string) => void;
|
||||
onChangePostInstallScript: (value?: string) => void;
|
||||
onChangeUninstallScript: (value?: string) => void;
|
||||
}
|
||||
|
||||
const AddPackageAdvancedOptions = ({
|
||||
errors,
|
||||
selectedPackage,
|
||||
preInstallQuery,
|
||||
installScript,
|
||||
postInstallScript,
|
||||
uninstallScript,
|
||||
onChangePreInstallQuery,
|
||||
onChangeInstallScript,
|
||||
onChangePostInstallScript,
|
||||
onChangeUninstallScript,
|
||||
}: IAddPackageAdvancedOptionsProps) => {
|
||||
const [showAdvancedOptions, setShowAdvancedOptions] = useState(false);
|
||||
|
||||
const renderAdvancedOptions = () => {
|
||||
const name = selectedPackage?.name || "";
|
||||
const ext = name.split(".").pop() as PackageType;
|
||||
if (!isPackageType(ext)) {
|
||||
// this should never happen
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<div className={`${baseClass}__input-fields`}>
|
||||
<FleetAce
|
||||
className="form-field"
|
||||
focus
|
||||
error={errors.preInstallQuery}
|
||||
value={preInstallQuery}
|
||||
placeholder="SELECT * FROM osquery_info WHERE start_time > 1"
|
||||
label="Pre-install query"
|
||||
name="preInstallQuery"
|
||||
maxLines={10}
|
||||
onChange={onChangePreInstallQuery}
|
||||
helpText={
|
||||
<>
|
||||
Software will be installed only if the{" "}
|
||||
<CustomLink
|
||||
className={`${baseClass}__table-link`}
|
||||
text="query returns results"
|
||||
url="https://fleetdm.com/tables"
|
||||
newTab
|
||||
/>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
<Editor
|
||||
wrapEnabled
|
||||
maxLines={10}
|
||||
name="install-script"
|
||||
onChange={onChangeInstallScript}
|
||||
value={installScript}
|
||||
helpText={PACKAGE_TYPES_TO_HELP_TEXT[ext].install.helpText}
|
||||
label="Install script"
|
||||
isFormField
|
||||
/>
|
||||
<Editor
|
||||
label="Post-install script"
|
||||
focus
|
||||
error={errors.postInstallScript}
|
||||
wrapEnabled
|
||||
name="post-install-script-editor"
|
||||
maxLines={10}
|
||||
onChange={onChangePostInstallScript}
|
||||
value={postInstallScript}
|
||||
helpText={PACKAGE_TYPES_TO_HELP_TEXT[ext].postInstall.helpText}
|
||||
isFormField
|
||||
/>
|
||||
<Editor
|
||||
label="Uninstall script"
|
||||
focus
|
||||
wrapEnabled
|
||||
name="uninstall-script-editor"
|
||||
maxLines={20}
|
||||
onChange={onChangeUninstallScript}
|
||||
value={uninstallScript}
|
||||
helpText={PACKAGE_TYPES_TO_HELP_TEXT[ext].uninstall.helpText}
|
||||
isFormField
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={baseClass}>
|
||||
<RevealButton
|
||||
@@ -37,63 +223,14 @@ const AddPackageAdvancedOptions = ({
|
||||
hideText="Advanced options"
|
||||
caretPosition="after"
|
||||
onClick={() => setShowAdvancedOptions(!showAdvancedOptions)}
|
||||
disabled={!selectedPackage}
|
||||
disabledTooltipContent={
|
||||
selectedPackage
|
||||
? "Choose a file to modify advanced options."
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
{showAdvancedOptions && (
|
||||
<div className={`${baseClass}__input-fields`}>
|
||||
<FleetAce
|
||||
className="form-field"
|
||||
focus
|
||||
error={errors.preInstallQuery}
|
||||
value={preInstallQuery}
|
||||
placeholder="SELECT * FROM osquery_info WHERE start_time > 1"
|
||||
label="Pre-install query"
|
||||
name="preInstallQuery"
|
||||
maxLines={10}
|
||||
onChange={onChangePreInstallQuery}
|
||||
helpText={
|
||||
<>
|
||||
Software will be installed only if the{" "}
|
||||
<CustomLink
|
||||
className={`${baseClass}__table-link`}
|
||||
text="query returns results"
|
||||
url="https://fleetdm.com/tables"
|
||||
newTab
|
||||
/>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
<Editor
|
||||
wrapEnabled
|
||||
maxLines={10}
|
||||
name="install-script"
|
||||
onChange={onChangeInstallScript}
|
||||
value={installScript}
|
||||
helpText="Shell (macOS and Linux) or PowerShell (Windows)."
|
||||
label="Install script"
|
||||
labelTooltip={
|
||||
<>
|
||||
Fleet will run this script on hosts to install software. Use the
|
||||
<br />
|
||||
$INSTALLER_PATH variable to point to the installer.
|
||||
</>
|
||||
}
|
||||
isFormField
|
||||
/>
|
||||
<Editor
|
||||
label="Post-install script"
|
||||
labelTooltip="Fleet will run this script after install."
|
||||
focus
|
||||
error={errors.postInstallScript}
|
||||
wrapEnabled
|
||||
name="post-install-script-editor"
|
||||
maxLines={10}
|
||||
onChange={onChangePostInstallScript}
|
||||
value={postInstallScript}
|
||||
helpText="Shell (macOS and Linux) or PowerShell (Windows)."
|
||||
isFormField
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{showAdvancedOptions && !!selectedPackage && renderAdvancedOptions()}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -2,7 +2,8 @@ import React, { useContext, useState } from "react";
|
||||
|
||||
import { NotificationContext } from "context/notification";
|
||||
import { getFileDetails } from "utilities/file/fileUtils";
|
||||
import getInstallScript from "utilities/software_install_scripts";
|
||||
import getDefaultInstallScript from "utilities/software_install_scripts";
|
||||
import getDefaultUninstallScript from "utilities/software_uninstall_scripts";
|
||||
|
||||
import Button from "components/buttons/Button";
|
||||
import Checkbox from "components/forms/fields/Checkbox";
|
||||
@@ -30,9 +31,10 @@ const UploadingSoftware = () => {
|
||||
|
||||
export interface IAddPackageFormData {
|
||||
software: File | null;
|
||||
installScript: string;
|
||||
preInstallQuery?: string;
|
||||
installScript: string;
|
||||
postInstallScript?: string;
|
||||
uninstallScript?: string;
|
||||
selfService: boolean;
|
||||
}
|
||||
|
||||
@@ -59,9 +61,10 @@ const AddPackageForm = ({
|
||||
|
||||
const [formData, setFormData] = useState<IAddPackageFormData>({
|
||||
software: null,
|
||||
installScript: "",
|
||||
preInstallQuery: undefined,
|
||||
installScript: "",
|
||||
postInstallScript: undefined,
|
||||
uninstallScript: undefined,
|
||||
selfService: false,
|
||||
});
|
||||
const [formValidation, setFormValidation] = useState<IFormValidation>({
|
||||
@@ -69,13 +72,21 @@ const AddPackageForm = ({
|
||||
software: { isValid: false },
|
||||
});
|
||||
|
||||
const onFileUpload = (files: FileList | null) => {
|
||||
const onFileSelect = (files: FileList | null) => {
|
||||
if (files && files.length > 0) {
|
||||
const file = files[0];
|
||||
|
||||
let installScript: string;
|
||||
let defaultInstallScript: string;
|
||||
try {
|
||||
installScript = getInstallScript(file.name);
|
||||
defaultInstallScript = getDefaultInstallScript(file.name);
|
||||
} catch (e) {
|
||||
renderFlash("error", `${e}`);
|
||||
return;
|
||||
}
|
||||
|
||||
let defaultUninstallScript: string;
|
||||
try {
|
||||
defaultUninstallScript = getDefaultUninstallScript(file.name);
|
||||
} catch (e) {
|
||||
renderFlash("error", `${e}`);
|
||||
return;
|
||||
@@ -84,7 +95,8 @@ const AddPackageForm = ({
|
||||
const newData = {
|
||||
...formData,
|
||||
software: file,
|
||||
installScript,
|
||||
installScript: defaultInstallScript,
|
||||
uninstallScript: defaultUninstallScript,
|
||||
};
|
||||
setFormData(newData);
|
||||
setFormValidation(generateFormValidation(newData));
|
||||
@@ -112,6 +124,12 @@ const AddPackageForm = ({
|
||||
setFormValidation(generateFormValidation(newData));
|
||||
};
|
||||
|
||||
const onChangeUninstallScript = (value?: string) => {
|
||||
const newData = { ...formData, uninstallScript: value };
|
||||
setFormData(newData);
|
||||
setFormValidation(generateFormValidation(newData));
|
||||
};
|
||||
|
||||
const onToggleSelfServiceCheckbox = (value: boolean) => {
|
||||
const newData = { ...formData, selfService: value };
|
||||
setFormData(newData);
|
||||
@@ -130,7 +148,7 @@ const AddPackageForm = ({
|
||||
graphicName={"file-pkg"}
|
||||
accept=".pkg,.msi,.exe,.deb"
|
||||
message=".pkg, .msi, .exe, or .deb"
|
||||
onFileUpload={onFileUpload}
|
||||
onFileUpload={onFileSelect}
|
||||
buttonMessage="Choose file"
|
||||
buttonType="link"
|
||||
className={`${baseClass}__file-uploader`}
|
||||
@@ -156,16 +174,19 @@ const AddPackageForm = ({
|
||||
</TooltipWrapper>
|
||||
</Checkbox>
|
||||
<AddPackageAdvancedOptions
|
||||
selectedPackage={formData.software}
|
||||
errors={{
|
||||
preInstallQuery: formValidation.preInstallQuery?.message,
|
||||
postInstallScript: formValidation.postInstallScript?.message,
|
||||
}}
|
||||
preInstallQuery={formData.preInstallQuery}
|
||||
installScript={formData.installScript}
|
||||
postInstallScript={formData.postInstallScript}
|
||||
uninstallScript={formData.uninstallScript}
|
||||
onChangePreInstallQuery={onChangePreInstallQuery}
|
||||
onChangeInstallScript={onChangeInstallScript}
|
||||
onChangePostInstallScript={onChangePostInstallScript}
|
||||
installScript={formData.installScript}
|
||||
onChangeUninstallScript={onChangeUninstallScript}
|
||||
/>
|
||||
<div className="modal-cta-wrap">
|
||||
<Button type="submit" variant="brand" disabled={isSubmitDisabled}>
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import validator from "validator";
|
||||
|
||||
// @ts-ignore
|
||||
import validateQuery from "components/forms/validators/validate_query";
|
||||
|
||||
@@ -7,7 +5,7 @@ import { IAddPackageFormData, IFormValidation } from "./AddPackageForm";
|
||||
|
||||
type IAddPackageFormValidatorKey = Exclude<
|
||||
keyof IAddPackageFormData,
|
||||
"installScript"
|
||||
"installScript" | "uninstallScript"
|
||||
>;
|
||||
|
||||
type IMessageFunc = (formData: IAddPackageFormData) => string;
|
||||
|
||||
@@ -132,6 +132,9 @@ $max-width: 2560px;
|
||||
font-size: $xx-small;
|
||||
font-weight: $regular;
|
||||
@include grey-text;
|
||||
.custom-link {
|
||||
font-size: inherit;
|
||||
}
|
||||
}
|
||||
|
||||
@mixin link {
|
||||
|
||||
@@ -11,7 +11,7 @@ import installDeb from "../../pkg/file/scripts/install_deb.sh";
|
||||
* getInstallScript returns a string with a script to install the
|
||||
* provided software.
|
||||
* */
|
||||
const getInstallScript = (fileName: string): string => {
|
||||
const getDefaultInstallScript = (fileName: string): string => {
|
||||
const extension = fileName.split(".").pop();
|
||||
switch (extension) {
|
||||
case "pkg":
|
||||
@@ -27,4 +27,4 @@ const getInstallScript = (fileName: string): string => {
|
||||
}
|
||||
};
|
||||
|
||||
export default getInstallScript;
|
||||
export default getDefaultInstallScript;
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
// @ts-ignore
|
||||
import uninstallPkg from "../../pkg/file/scripts/uninstall_pkg.sh";
|
||||
// @ts-ignore
|
||||
import uninstallMsi from "../../pkg/file/scripts/uninstall_msi.ps1";
|
||||
// @ts-ignore
|
||||
import uninstallExe from "../../pkg/file/scripts/uninstall_exe.ps1";
|
||||
// @ts-ignore
|
||||
import uninstallDeb from "../../pkg/file/scripts/uninstall_deb.sh";
|
||||
|
||||
/*
|
||||
* getUninstallScript returns a string with a script to uninstall the
|
||||
* provided software.
|
||||
* */
|
||||
const getDefaultUninstallScript = (fileName: string): string => {
|
||||
const extension = fileName.split(".").pop();
|
||||
switch (extension) {
|
||||
case "pkg":
|
||||
return uninstallPkg;
|
||||
case "msi":
|
||||
return uninstallMsi;
|
||||
case "deb":
|
||||
return uninstallDeb;
|
||||
case "exe":
|
||||
return uninstallExe;
|
||||
default:
|
||||
throw new Error(`unsupported file extension: ${extension}`);
|
||||
}
|
||||
};
|
||||
|
||||
export default getDefaultUninstallScript;
|
||||
@@ -1,16 +1,20 @@
|
||||
# Learn more about .exe install scripts: http://fleetdm.com/learn-more-about/exe-install-scripts
|
||||
|
||||
$exeFilePath = "${env:INSTALLER_PATH}"
|
||||
|
||||
# extract the name of the executable to use as the sub-directory name
|
||||
$exeName = [System.IO.Path]::GetFileName($exeFilePath)
|
||||
$subDir = [System.IO.Path]::GetFileNameWithoutExtension($exeFilePath)
|
||||
|
||||
$destinationPath = Join-Path -Path $env:ProgramFiles -ChildPath $subDir
|
||||
|
||||
# check if the directory does not exist, and create it if necessary
|
||||
if (-not (Test-Path -Path $destinationPath)) {
|
||||
New-Item -ItemType Directory -Path $destinationPath
|
||||
# Add argument to install silently
|
||||
# Argument to make install silent depends on installer,
|
||||
# each installer might use different argument (usually it's "/S" or "/s")
|
||||
$processOptions = @{
|
||||
FilePath = "$exeFilePath"
|
||||
ArgumentList = "/S"
|
||||
PassThru = $true
|
||||
Wait = $true
|
||||
}
|
||||
|
||||
# Start process and track exit code
|
||||
$process = Start-Process @processOptions
|
||||
$exitCode = $process.ExitCode
|
||||
|
||||
# copy the .exe file to the new sub-directory
|
||||
$destinationExePath = Join-Path -Path $destinationPath -ChildPath $exeName
|
||||
Copy-Item -Path $exeFilePath -Destination $destinationExePath
|
||||
# Prints the exit code
|
||||
Write-Host "Install exit code: $exitCode"
|
||||
@@ -0,0 +1,4 @@
|
||||
$package_name=$PACKAGE_ID
|
||||
|
||||
# Fleet uninstalls app using product name that's extracted on upload
|
||||
apt remove $package_name
|
||||
@@ -0,0 +1,17 @@
|
||||
# Fleet extracts name from installer (EXE) and saves it to package ID variable
|
||||
$softwareName = $PACKAGE_ID
|
||||
|
||||
# Get the list of subkeys under the Uninstall registry path
|
||||
$uninstallKeys = Get-ChildItem "HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall" | ForEach-Object { Get-ItemProperty $_.PSPath }
|
||||
|
||||
# Loop through each registry key to find the one containing "$softwareName" in DisplayName and run uninstall command from UninstallString
|
||||
foreach ($key in $uninstallKeys) {
|
||||
if ($key.DisplayName -like "*$softwareName*") {
|
||||
# Get the uninstall command
|
||||
$uninstallCommand = if ($key.QuietUninstallString) { $key.QuietUninstallString } else { $key.UninstallString }
|
||||
|
||||
# Run the uninstall command with arguments using the call operator &
|
||||
& $uninstallCommand
|
||||
break # Exit the loop once the software is found and uninstalled
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
$product_code = $PACKAGE_ID
|
||||
|
||||
# Fleet uninstalls app using product code that's extracted on upload
|
||||
msiexec /x $product_code
|
||||
@@ -0,0 +1,17 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Fleet extracts and saves package IDs
|
||||
pkg_ids=$PACKAGE_ID
|
||||
|
||||
# Get all files associated with package and remove them
|
||||
for pkg_id in "${pkg_ids[@]}"
|
||||
do
|
||||
pkgutil --files $pkg_id | tr '\n' '\0' | xargs -n 1 -0 rm -d
|
||||
done
|
||||
|
||||
# Loop through each pkg_id and remove receipts
|
||||
|
||||
for pkg_id in "${pkg_ids[@]}"
|
||||
do
|
||||
pkgutil --forget $pkg_id
|
||||
done
|
||||
Reference in New Issue
Block a user