Skip to content

Commit

Permalink
Improve uninstall workflow (#124)
Browse files Browse the repository at this point in the history
Mimic winget uninstall feature by implementing regex that Microsoft uses. This makes uninstalls for applications with no uninstall directive more reliable.
  • Loading branch information
sbrito85 authored Oct 9, 2024
1 parent e08522f commit 8ba881b
Show file tree
Hide file tree
Showing 2 changed files with 68 additions and 12 deletions.
44 changes: 44 additions & 0 deletions system/system.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,50 @@ import (
"github.com/google/logger"
)

// Regex taken from Winget uninstaller
// https://github.com/microsoft/winget-cli/blob/6ea13623e5e4b870b81efeea9142d15a98dd4208/src/AppInstallerCommonCore/NameNormalization.cpp#L262
var (
programNameReg = []string{
"PrefixParens",
"EmptyParens",
"Version",
"TrailingSymbols",
"LeadingSymbols",
"FilePathParens",
"FilePathQuotes",
"FilePath",
"VersionLetter",
"VersionLetterDelimited",
"En",
"NonNestedBracket",
"BracketEnclosed",
"URIProtocol",
}
publisherNameReg = []string{
"VersionDelimited",
"Version",
"NonNestedBracket",
"BracketEnclosed",
"URIProtocol",
}
regex = map[string]string{
"PrefixParens": `(^\(.*?\))`,
"EmptyParens": `((\(\s*\)|\[\s*\]|"\s*"))`,
"Version": `(?:^)|(?:P|V|R|VER|VERSI(?:O|Ó)N|VERSÃO|VERSIE|WERSJA|BUILD|RELEASE|RC|SP)(?:\P{L}|\P{L}\p{L})?(\p{Nd}|\.\p{Nd})+(?:RC|B|A|R|V|SP)?\p{Nd}?`,
"TrailingSymbols": `([^\p{L}\p{Nd}]+$)`,
"LeadingSymbols": `(^[^\p{L}\p{Nd}]+)`,
"FilePathParens": `(\([CDEF]:\\(.+?\\)*[^\s]*\\?\))`,
"FilePathQuotes": `("[CDEF]:\\(.+?\\)*[^\s]*\\?")`,
"FilePath": `(((INSTALLED\sAT|IN)\s)?[CDEF]:\\(.+?\\)*[^\s]*\\?)`,
"VersionLetter": `((?:^\p{L})(?:(?:V|VER|VERSI(?:O|Ó)N|VERSÃO|VERSIE|WERSJA|BUILD|RELEASE|RC|SP)\P{L})?\p{Lu}\p{Nd}+(?:[\p{Po}\p{Pd}\p{Pc}]\p{Nd}+)+)`,
"VersionLetterDelimited": `((?:^\p{L})(?:(?:V|VER|VERSI(?:O|Ó)N|VERSÃO|VERSIE|WERSJA|BUILD|RELEASE|RC|SP)\P{L})?\p{Lu}\p{Nd}+(?:[\p{Po}\p{Pd}\p{Pc}]\p{Nd}+)+)`,
"En": `(\sEN\s*$)`,
"NonNestedBracket": `(\([^\(\)]*\)|\[[^\[\]]*\])`,
"BracketEnclosed": `((?:\p{Ps}.*\p{Pe}|".*"))`,
"URIProtocol": `((?:^\p{L})(?:http[s]?|ftp):\/\/)`,
}
)

// Verify runs a verify command given a package extraction directory and a PkgSpec struct.
func Verify(dir string, ps *goolib.PkgSpec) error {
v := ps.Verify
Expand Down
36 changes: 24 additions & 12 deletions system/system_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ func removeUninstallEntry(name string) error {
return registry.DeleteKey(registry.LOCAL_MACHINE, reg)
}

func uninstallString(installSource, extension string) string {
func uninstallString(publisher, installSource, programName, extension string) string {
productroot := `SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\`
k, err := registry.OpenKey(registry.LOCAL_MACHINE, productroot, registry.ENUMERATE_SUB_KEYS)
if err != nil {
Expand Down Expand Up @@ -102,26 +102,38 @@ func uninstallString(installSource, extension string) string {
return un
}
default:
displayName, _, err := q.GetStringValue("DisplayName")
for _, v := range publisherNameReg {
re := regexp.MustCompile("(?i)" + regex[v])
publisher = re.ReplaceAllString(publisher, "")
}
for _, v := range programNameReg {
re := regexp.MustCompile("(?i)" + regex[v])
programName = re.ReplaceAllString(programName, "")
}
publisherReg, _, err := q.GetStringValue("Publisher")
if err != nil {
continue
}
// Remove display name whitespace to conform to package name
if strings.ToLower(strings.ReplaceAll(displayName, " ", "")) == strings.ToLower(installSource) {
// Check if the value exists, move on if it doesn't
un, _, err := q.GetStringValue("QuietUninstallString")
if err != nil {
un, _, err = q.GetStringValue("UninstallString")
if strings.ToLower(publisherReg) == strings.ToLower(publisher) {
displayName, _, _ := q.GetStringValue("DisplayName")
if strings.Contains(strings.ToLower(programName), strings.ToLower(strings.ReplaceAll(displayName, " ", ""))) {
// Check if the value exists, move on if it doesn't
un, _, err := q.GetStringValue("QuietUninstallString")
if err != nil {
// UninstallString not found, move on to next entry
continue
un, _, err = q.GetStringValue("UninstallString")
if err != nil {
// UninstallString not found, move on to next entry
continue
}
}
return un
}
return un
}
}
}
return ""

}

// Install performs a system specfic install given a package extraction directory and a PkgSpec struct.
Expand Down Expand Up @@ -183,7 +195,7 @@ func Uninstall(dir string, ps *goolib.PkgSpec) error {
if un.Path == "" {
switch filepath.Ext(ps.Install.Path) {
case ".msi":
u := uninstallString(dir, "msi")
u := uninstallString(ps.Authors, dir, ps.Name, "msi")
u = strings.ReplaceAll(u, `/I`, `/X`)
commands := strings.Split(u, " ")
un.Path = commands[0]
Expand All @@ -195,7 +207,7 @@ func Uninstall(dir string, ps *goolib.PkgSpec) error {
filePath = un.Path
default:
r := regexp.MustCompile(`[^\s"]+|"([^"]*)"`)
u := uninstallString(ps.Name, "")
u := uninstallString(ps.Authors, dir, ps.Name, "")
commands := r.FindAllString(u, -1)
if len(commands) > 0 {
// Remove the quotes from the install string since we handle that below
Expand Down

0 comments on commit 8ba881b

Please sign in to comment.