packages/msi_windows.go (149 lines of code) (raw):

// Copyright 2020 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package packages import ( "context" "fmt" "strings" "sync" "syscall" "unsafe" "github.com/GoogleCloudPlatform/osconfig/clog" ole "github.com/go-ole/go-ole" "golang.org/x/sys/windows" ) var ( msiInstallArgs = []string{"ACTION=INSTALL", "REBOOT=ReallySuppress"} msi = windows.NewLazySystemDLL("msi.dll") procMsiOpenPackageExW = msi.NewProc("MsiOpenPackageExW") procMsiGetProductPropertyW = msi.NewProc("MsiGetProductPropertyW") procMsiQueryProductStateW = msi.NewProc("MsiQueryProductStateW") procMsiCloseHandle = msi.NewProc("MsiCloseHandle") procMsiInstallProductW = msi.NewProc("MsiInstallProductW") procMsiSetInternalUI = msi.NewProc("MsiSetInternalUI") once sync.Once ) func init() { MSIExists = true } func setUIMode() { /* INSTALLUILEVEL MsiSetInternalUI( INSTALLUILEVEL dwUILevel, HWND *phWnd ); */ const INSTALLUILEVEL_NONE = 2 once.Do(func() { procMsiSetInternalUI.Call( uintptr(INSTALLUILEVEL_NONE), 0, ) }) } // https://docs.microsoft.com/en-us/windows/win32/api/msi/nf-msi-msiopenpackageexw func msiOpenPackageExW(szPackagePath string, dwOptions uint32) (uintptr, error) { /* UINT MsiOpenPackageExW( LPCWSTR szPackagePath, DWORD dwOptions, MSIHANDLE *hProduct ); */ var handle int32 pHandle := uintptr(unsafe.Pointer(&handle)) szPackagePathPtr, err := syscall.UTF16PtrFromString(szPackagePath) if err != nil { return 0, fmt.Errorf("error encoding szPackagePath to UTF16: %v", err) } ret, _, _ := procMsiOpenPackageExW.Call( uintptr(unsafe.Pointer(szPackagePathPtr)), uintptr(dwOptions), pHandle, ) if ret != 0 { return 0, fmt.Errorf("MsiOpenPackageExW error: %s", syscall.Errno(ret)) } return uintptr(handle), nil } // https://docs.microsoft.com/en-us/windows/win32/api/msi/nf-msi-msiclosehandle func msiCloseHandle(handle uintptr) { /* UINT MsiCloseHandle( MSIHANDLE hAny ); */ procMsiCloseHandle.Call(handle) } // https://docs.microsoft.com/en-us/windows/win32/api/msi/nf-msi-msigetproductpropertyw func msiGetProductPropertyW(handle uintptr, szProperty string) (string, error) { /* UINT MsiGetProductPropertyW( MSIHANDLE hProduct, LPCSTR szProperty, LPSTR lpValueBuf, LPDWORD pcchValueBuf ); */ szPropertyPtr, err := syscall.UTF16PtrFromString(szProperty) if err != nil { return "", fmt.Errorf("error encoding szProperty to UTF16: %v", err) } size := uint32(128) lpValueBuf := make([]uint16, size) ret, _, _ := procMsiGetProductPropertyW.Call( handle, uintptr(unsafe.Pointer(szPropertyPtr)), uintptr(unsafe.Pointer(&lpValueBuf[0])), uintptr(unsafe.Pointer(&size)), ) if ret != 0 { return "", fmt.Errorf("MsiGetProductPropertyW error: %s", syscall.Errno(ret)) } return syscall.UTF16ToString(lpValueBuf), nil } type msiInstallState int32 const ( INSTALLSTATE_UNKNOWN = msiInstallState(-1) INSTALLSTATE_ADVERTISED = msiInstallState(1) INSTALLSTATE_ABSENT = msiInstallState(2) INSTALLSTATE_DEFAULT = msiInstallState(5) ) // https://docs.microsoft.com/en-us/windows/win32/api/msi/nf-msi-msiqueryproductstatew func msiMsiQueryProductStateW(szProduct string) (msiInstallState, error) { /* INSTALLSTATE MsiQueryProductStateW( LPCWSTR szProduct ); */ szProductPtr, err := syscall.UTF16PtrFromString(szProduct) if err != nil { return -1, fmt.Errorf("error encoding szProduct to UTF16: %v", err) } ret, _, _ := procMsiQueryProductStateW.Call(uintptr(unsafe.Pointer(szProductPtr))) return msiInstallState(ret), nil } // https://docs.microsoft.com/en-us/windows/win32/api/msi/nf-msi-msiinstallproductw func msiInstallProductW(szPackagePath string, szCommandLine []string) error { /* UINT MsiInstallProductW( LPCWSTR szPackagePath, LPCWSTR szCommandLine ); */ szPackagePathPtr, err := syscall.UTF16PtrFromString(szPackagePath) if err != nil { return fmt.Errorf("error encoding szPackagePath to UTF16: %v", err) } szCommandLinePtr, err := syscall.UTF16PtrFromString(strings.Join(szCommandLine, " ")) if err != nil { return fmt.Errorf("error encoding szCommandLine to UTF16: %v", err) } ret, _, _ := procMsiInstallProductW.Call( uintptr(unsafe.Pointer(szPackagePathPtr)), uintptr(unsafe.Pointer(szCommandLinePtr)), ) if ret != 0 { return fmt.Errorf("MsiInstallProductW error: %s", syscall.Errno(ret)) } return nil } // MSIInfo returns the ProductName and ProductCode for an MSI. func MSIInfo(path string) (string, string, error) { setUIMode() if err := coInitializeEx(); err != nil { return "", "", err } defer ole.CoUninitialize() const MSIOPENPACKAGEFLAGS_IGNOREMACHINESTATE = 1 handle, err := msiOpenPackageExW(path, MSIOPENPACKAGEFLAGS_IGNOREMACHINESTATE) if err != nil { return "", "", fmt.Errorf("error opening MSI package %q: %v", path, err) } defer msiCloseHandle(handle) productCode, err := msiGetProductPropertyW(handle, "ProductCode") if err != nil { return "", "", fmt.Errorf("error getting ProductCode property: %v", err) } productName, err := msiGetProductPropertyW(handle, "ProductName") if err != nil { return "", "", fmt.Errorf("error getting ProductName property: %v", err) } return productName, productCode, nil } // MSIInstalled returns if the msi ProductCode is installed. func MSIInstalled(productCode string) (bool, error) { setUIMode() if err := coInitializeEx(); err != nil { return false, err } defer ole.CoUninitialize() state, err := msiMsiQueryProductStateW(productCode) if err != nil { return false, err } return state == INSTALLSTATE_DEFAULT, nil } // InstallMSIPackage installs an msi package. func InstallMSIPackage(ctx context.Context, path string, args []string) error { setUIMode() args = append(msiInstallArgs, args...) clog.Infof(ctx, "Installing msi package %q with command line %q.", path, args) if err := msiInstallProductW(path, args); err != nil { return fmt.Errorf("error installing MSI package %q: %v", path, err) } return nil }