Created
April 19, 2023 00:29
-
-
Save heaths/c0dbb3431bd031ef3416842e82998825 to your computer and use it in GitHub Desktop.
Get the Windows Installer product assignment for products related by UpgradeCode
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//go:build windows | |
package main | |
import ( | |
"flag" | |
"fmt" | |
"log" | |
"os" | |
"path/filepath" | |
"syscall" | |
"unsafe" | |
"golang.org/x/sys/windows" | |
) | |
type INSTALLPROPERTY string | |
const ( | |
INSTALLPROPERTY_ASSIGNMENTTYPE INSTALLPROPERTY = "AssignmentType" | |
) | |
var ( | |
modmsi = windows.NewLazySystemDLL("msi.dll") | |
procMsiEnumRelatedProductsW = modmsi.NewProc("MsiEnumRelatedProductsW") | |
procMsiGetProductInfoW = modmsi.NewProc("MsiGetProductInfoW") | |
) | |
func main() { | |
verbose := flag.Bool("v", false, "Log verbose output") | |
flag.Usage = func() { | |
fmt.Fprintf(os.Stderr, "Check the state of a Windows Installer Product\n\n") | |
fmt.Fprintf(os.Stderr, "Usage of %s:\n\b", os.Args[0]) | |
fmt.Fprintf(os.Stderr, " %s [flags] UpgradeCode\n\n", filepath.Base(os.Args[0])) | |
fmt.Fprintf(os.Stderr, "Arguments\n") | |
fmt.Fprintf(os.Stderr, " UpgradeCode\n") | |
fmt.Fprintf(os.Stderr, " UpgradeCode of the product(s) to check\n") | |
fmt.Fprintln(os.Stderr) | |
fmt.Fprintf(os.Stderr, "Flags\n") | |
flag.PrintDefaults() | |
} | |
flag.Parse() | |
upgradeCode := flag.Arg(0) | |
if upgradeCode == "" { | |
log.Fatal("requires UpgradeCode") | |
} | |
productCodes, err := GetRelatedProducts(upgradeCode) | |
if err != nil { | |
log.Fatalf("failed to get related products: %s", err) | |
} | |
for _, productCode := range productCodes { | |
if *verbose { | |
log.Printf("found %s\n", productCode) | |
} | |
data, err := MsiGetProductInfo(productCode, INSTALLPROPERTY_ASSIGNMENTTYPE) | |
if err != nil { | |
log.Fatalf("failed to get product assignment: %s", err) | |
} | |
var scope string | |
if data == "1" { | |
scope = "per-machine" | |
} else { | |
scope = "per-user" | |
} | |
fmt.Printf("%s is installed %s\n", productCode, scope) | |
} | |
} | |
func GetRelatedProducts(upgradeCode string) ([]string, error) { | |
productCodes := make([]string, 0) | |
i := uint32(0) | |
for { | |
productCode, err := MsiEnumRelatedProducts(upgradeCode, i) | |
if err == nil { | |
productCodes = append(productCodes, productCode) | |
} else if err == windows.ERROR_BAD_CONFIGURATION { | |
continue | |
} else if err == windows.ERROR_NO_MORE_ITEMS { | |
break | |
} else { | |
return nil, err | |
} | |
i++ | |
} | |
return productCodes, nil | |
} | |
func MsiEnumRelatedProducts(upgradeCode string, i uint32) (string, error) { | |
lpUpgradeCode, err := syscall.UTF16PtrFromString(upgradeCode) | |
if err != nil { | |
return "", err | |
} | |
lpProductBuf := make([]uint16, 39) | |
r1, _, err := syscall.SyscallN( | |
procMsiEnumRelatedProductsW.Addr(), | |
uintptr(unsafe.Pointer(lpUpgradeCode)), | |
0, | |
uintptr(i), | |
uintptr(unsafe.Pointer(&lpProductBuf[0])), | |
) | |
if r1 == uintptr(windows.ERROR_SUCCESS) { | |
return syscall.UTF16ToString(lpProductBuf), nil | |
} else if r1 == uintptr(windows.ERROR_BAD_CONFIGURATION) { | |
return "", windows.ERROR_BAD_CONFIGURATION | |
} else if r1 == uintptr(windows.ERROR_NO_MORE_ITEMS) { | |
return "", windows.ERROR_NO_MORE_ITEMS | |
} | |
return "", err | |
} | |
func MsiGetProductInfo(productCode string, property INSTALLPROPERTY) (string, error) { | |
szProduct, err := syscall.UTF16PtrFromString(productCode) | |
if err != nil { | |
return "", err | |
} | |
szAttribute, err := syscall.UTF16PtrFromString(string(property)) | |
if err != nil { | |
return "", err | |
} | |
var pcchValueBuf uint32 | |
r1, _, err := syscall.SyscallN( | |
procMsiGetProductInfoW.Addr(), | |
uintptr(unsafe.Pointer(szProduct)), | |
uintptr(unsafe.Pointer(szAttribute)), | |
uintptr(unsafe.Pointer(nil)), | |
uintptr(unsafe.Pointer(&pcchValueBuf)), | |
) | |
if r1 != uintptr(windows.ERROR_SUCCESS) { | |
return "", err | |
} | |
pcchValueBuf++ | |
lpValueBuf := make([]uint16, pcchValueBuf) | |
r1, _, err = syscall.SyscallN( | |
procMsiGetProductInfoW.Addr(), | |
uintptr(unsafe.Pointer(szProduct)), | |
uintptr(unsafe.Pointer(szAttribute)), | |
uintptr(unsafe.Pointer(&lpValueBuf[0])), | |
uintptr(unsafe.Pointer(&pcchValueBuf)), | |
) | |
if r1 == uintptr(windows.ERROR_SUCCESS) { | |
return syscall.UTF16ToString(lpValueBuf), nil | |
} | |
return "", nil | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment