在 Go 中创建 Windows 快捷方式 (.lnk)

Create a Windows Shortcut (.lnk) in Go

我想在 Golang 中创建一个 Windows 桌面和开始菜单的快捷方式 (.lnk)。

我实际上是通过 gowin 模块获得了桌面和开始菜单文件夹,我想创建到这些位置的快捷方式。

我搜索过,但没有找到任何 golang 项目。我应该创建它吗?还有其他漂亮的方法吗?

不,在golang中没有任何创建.lnk文件的漂亮方法。

主要原因是,.lnk 文件是 windows 特定的。

在Windows中,即使是本机程序也需要使用OLE(对象链接和嵌入)和COM(组件对象模型)来创建快捷方式文件,如this answer中所述。

在我看来,在 golang 中解决这个问题的一种方法是使用 gowin,并尝试与 OLE COM 通信。

编写一个本机 windows 组件来完成创建 .lnk 文件的实际工作,并通过您的 go 程序生成它的进程。

通过来自 this subject 的外部程序的解决方案:

来自 NirSoft

的快捷方式可执行文件
shortcut "f:\winnt\system32\calc.exe" "~$folder.desktop$" "Windows Calculator" 
shortcut "f:\winnt\system32\calc.exe" "~$folder.programs$\Calculators" "Windows Calculator" 
shortcut "f:\Program Files\KaZaA\Kazaa.exe" "c:\temp\MyShortcuts" "Kazaa" 
shortcut "f:\Program Files" "c:\temp\MyShortcuts" "Program Files Folder" "" "f:\winnt\system32\shell32.dll" 45 
shortcut "f:\Program Files" "c:\temp\MyShortcuts" "Program Files Folder" "" "" "" "max"

来自 Optimumx

的快捷方式可执行文件
Shortcut.exe /f:"%USERPROFILE%\Desktop\sc.lnk" /a:c  /t:%USERPROFILE%\Desktop\scrum.pdf

.vbs

Set oWS = WScript.CreateObject("WScript.Shell")
sLinkFile = "C:\MyShortcut.LNK"
Set oLink = oWS.CreateShortcut(sLinkFile)
    oLink.TargetPath = "C:\Program Files\MyApp\MyProgram.EXE"
 '  oLink.Arguments = ""
 '  oLink.Description = "MyProgram"   
 '  oLink.HotKey = "ALT+CTRL+F"
 '  oLink.IconLocation = "C:\Program Files\MyApp\MyProgram.EXE, 2"
 '  oLink.WindowStyle = "1"   
 '  oLink.WorkingDirectory = "C:\Program Files\MyApp"
oLink.Save

Powershell 脚本

set TARGET='D:\Temp'
set SHORTCUT='C:\Temp\test.lnk'
set PWS=powershell.exe -ExecutionPolicy Bypass -NoLogo -NonInteractive -NoProfile

%PWS% -Command "$ws = New-Object -ComObject WScript.Shell; $s = $ws.CreateShortcut(%SHORTCUT%); $S.TargetPath = %TARGET%; $S.Save()"

使用 VBS 的 AWFUL Working golang 解决方案;

package main

import(
    "bytes"
    "fmt"
    "io/ioutil"
    "os"
    "os/exec"
)

func createShortcut(linkName string, target string, arguments string, directory string, description string, destination string) {
    var scriptTxt bytes.Buffer
    scriptTxt.WriteString("option explicit\n\n")
    scriptTxt.WriteString("sub CreateShortCut()\n")
    scriptTxt.WriteString("dim objShell, strDesktopPath, objLink\n")
    scriptTxt.WriteString("set objShell = CreateObject(\"WScript.Shell\")\n")
    scriptTxt.WriteString("strDesktopPath = objShell.SpecialFolders(\"")
    scriptTxt.WriteString(destination)
    scriptTxt.WriteString("\")\n")
    scriptTxt.WriteString("set objLink = objShell.CreateShortcut(strDesktopPath & \"\")
    scriptTxt.WriteString(linkName)
    scriptTxt.WriteString(".lnk\")\n")
    scriptTxt.WriteString("objLink.Arguments = \"")
    scriptTxt.WriteString(arguments)
    scriptTxt.WriteString("\"\n")
    scriptTxt.WriteString("objLink.Description = \"")
    scriptTxt.WriteString(description)
    scriptTxt.WriteString("\"\n")
    scriptTxt.WriteString("objLink.TargetPath = \"")
    scriptTxt.WriteString(target)
    scriptTxt.WriteString("\"\n")
    scriptTxt.WriteString("objLink.WindowStyle = 1\n")
    scriptTxt.WriteString("objLink.WorkingDirectory = \"")
    scriptTxt.WriteString(directory)
    scriptTxt.WriteString("\"\n")
    scriptTxt.WriteString("objLink.Save\nend sub\n\n")
    scriptTxt.WriteString("call CreateShortCut()")
    fmt.Print(scriptTxt.String())

    filename := fmt.Sprintf("lnkTo%s.vbs", destination)
    ioutil.WriteFile(filename, scriptTxt.Bytes(), 0777)
    cmd := exec.Command("wscript", filename)
    err := cmd.Run()
    if err != nil {
        fmt.Println(err)
    }
    cmd.Wait()
    os.Remove(filename)
    return
}

使用https://github.com/go-ole/go-ole:

func makeLink(src, dst string) error {
    ole.CoInitializeEx(0, ole.COINIT_APARTMENTTHREADED|ole.COINIT_SPEED_OVER_MEMORY)
    oleShellObject, err := oleutil.CreateObject("WScript.Shell")
    if err != nil {
        return err
    }
    defer oleShellObject.Release()
    wshell, err := oleShellObject.QueryInterface(ole.IID_IDispatch)
    if err != nil {
        return err
    }
    defer wshell.Release()
    cs, err := oleutil.CallMethod(wshell, "CreateShortcut", dst)
    if err != nil {
        return err
    }
    idispatch := cs.ToIDispatch()
    oleutil.PutProperty(idispatch, "TargetPath", src)
    oleutil.CallMethod(idispatch, "Save")
    return nil
}

如果出于任何原因您不想使用外部 go 包,这里有一个替代方案。 如 所述,您可以使用 Powershell 在 Windows 下创建快捷方式。优点是它已经在几乎所有 Windows 环境中可用。这是在 shell:startup 文件夹中创建快捷方式的实现,它将在启动时自动为当前用户启动链接程序:

package main

import (
    "bytes"
    "log"
    "os/exec"
    "strings"
)

type PowerShell struct {
    powerShell string
}

var WIN_CREATE_SHORTCUT = `$WshShell = New-Object -comObject WScript.Shell
$Shortcut = $WshShell.CreateShortcut("$HOME\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup\MyAPP.lnk")
$Shortcut.TargetPath = "PLACEHOLDER"
$Shortcut.Save()`

// New create new session
func New() *PowerShell {
    ps, _ := exec.LookPath("powershell.exe")
    return &PowerShell{
        powerShell: ps,
    }
}

func (p *PowerShell) execute(args ...string) (stdOut string, stdErr string, err error) {
    args = append([]string{"-NoProfile", "-NonInteractive"}, args...)
    cmd := exec.Command(p.powerShell, args...)

    var stdout bytes.Buffer
    var stderr bytes.Buffer
    cmd.Stdout = &stdout
    cmd.Stderr = &stderr

    err = cmd.Run()
    stdOut, stdErr = stdout.String(), stderr.String()
    return
}

// enableAutostartWin creates a shortcut to MyAPP in the shell:startup folder
func enableAutostartWin() {
    ps := New()
    exec_path := "C:\MyAPP.exe"
    WIN_CREATE_SHORTCUT = strings.Replace(WIN_CREATE_SHORTCUT, "PLACEHOLDER", exec_path, 1)
    stdOut, stdErr, err := ps.execute(WIN_CREATE_SHORTCUT)
    log.Printf("CreateShortcut:\nStdOut : '%s'\nStdErr: '%s'\nErr: %s",
        strings.TrimSpace(stdOut), stdErr, err)
}

此答案基于 this SO answer and this gist