github.com/ActiveState/cli@v0.0.0-20240508170324-6801f60cd051/internal/runners/deploy/link_win.go (about)

     1  //go:build windows
     2  // +build windows
     3  
     4  package deploy
     5  
     6  import (
     7  	"strings"
     8  
     9  	"github.com/go-ole/go-ole"
    10  	"github.com/go-ole/go-ole/oleutil"
    11  
    12  	"github.com/ActiveState/cli/internal/errs"
    13  	"github.com/ActiveState/cli/internal/fileutils"
    14  	"github.com/ActiveState/cli/internal/locale"
    15  	"github.com/ActiveState/cli/internal/logging"
    16  )
    17  
    18  func shouldSkipSymlink(symlink, fpath string) (bool, error) {
    19  	// If the existing symlink already matches the one we want to create, skip it
    20  	if fileutils.FileExists(symlink) {
    21  		shortcut, err := newShortcut(symlink)
    22  		if err != nil {
    23  			return false, errs.Wrap(err, "Could not create shortcut interface")
    24  		}
    25  
    26  		symlinkTarget, err := oleutil.GetProperty(shortcut, "TargetPath")
    27  		if err != nil {
    28  			return false, locale.WrapError(err, "err_link_target", "Could not resolve target of link: {{.V0}}", symlink)
    29  		}
    30  
    31  		isAccurate, err := fileutils.PathsEqual(fpath, symlinkTarget.ToString())
    32  		if err != nil {
    33  			return false, locale.WrapError(err, "err_symlink_accuracy_unknown", "Could not determine whether link is owned by State Tool: {{.V0}}.", symlink)
    34  		}
    35  		if isAccurate {
    36  			return true, nil
    37  		}
    38  	}
    39  
    40  	return false, nil
    41  }
    42  
    43  func link(fpath, symlink string) error {
    44  	if strings.HasSuffix(symlink, ".exe") {
    45  		symlink = strings.Replace(symlink, ".exe", ".lnk", 1)
    46  	}
    47  	logging.Debug("Creating shortcut, destination: %s symlink: %s", fpath, symlink)
    48  
    49  	shortcut, err := newShortcut(symlink)
    50  	if err != nil {
    51  		return errs.Wrap(err, "Could not create shortcut interface")
    52  	}
    53  
    54  	if _, err = oleutil.PutProperty(shortcut, "TargetPath", fpath); err != nil {
    55  		return errs.Wrap(err, "Could not set TargetPath on lnk")
    56  	}
    57  	if _, err = oleutil.CallMethod(shortcut, "Save"); err != nil {
    58  		return errs.Wrap(err, "Could not save lnk")
    59  	}
    60  	return nil
    61  }
    62  
    63  func newShortcut(target string) (*ole.IDispatch, error) {
    64  	// ALWAYS errors with "Incorrect function", which can apparently be safely ignored..
    65  	_ = ole.CoInitialize(0)
    66  
    67  	oleShellObject, err := oleutil.CreateObject("WScript.Shell")
    68  	if err != nil {
    69  		return nil, errs.Wrap(err, "Could not create shell object")
    70  	}
    71  
    72  	defer oleShellObject.Release()
    73  	wshell, err := oleShellObject.QueryInterface(ole.IID_IDispatch)
    74  	if err != nil {
    75  		return nil, errs.Wrap(err, "Could not interface with shell object")
    76  	}
    77  
    78  	defer wshell.Release()
    79  	cs, err := oleutil.CallMethod(wshell, "CreateShortcut", target)
    80  	if err != nil {
    81  		return nil, errs.Wrap(err, "Could not call CreateShortcut on shell object")
    82  	}
    83  	return cs.ToIDispatch(), nil
    84  }