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
}