| // 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 |
| } |