4

I would like to create a Windows Shortcut (.lnk) to the desktop and the startmenu in Golang.

I actually got the Desktop & Startmenu folders via the gowin module and I would like to create a shortcut to thoses locations.

I searched but I did not find any golang project for it. Should I create it ? Is there an other pretty method ?

1
  • I don't know windows and don't have access to it to test. However, AFAIU lnk is the equivalent of a symlink on *nix. So I would try os.Symlink as I would expect that to work cross platform unless otherwise documented. (I may be conflating symlinks and shortcuts). Commented Sep 7, 2015 at 15:35

5 Answers 5

9

Using 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
}
Sign up to request clarification or add additional context in comments.

3 Comments

One year later, I finally have a true solution! Thanks :D
After running this in production for many months, I've noticed some rare failures with the message "CoInitialize has not been called.". To fix this you need to ensure that runtime.LockOSThread() is called, or use the comshim library as outlined in github.com/go-ole/go-ole/issues/124
Adding here that dst and src should be of type string. This is automatically made into an appropriate UTF16 encoding under the hood as needed.
3

Solution via external program from this subject:

Shortcut executable from 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"

Shortcut executable from 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 script

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()"

Comments

2

The AWFUL Working golang solution using VBS;

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
}

1 Comment

You could use back-quoted Go string literals to avoid needing to escape quotes (\"). You can also use those to make readable multi-line string literals to decrease the number of WriteString calls you make. You could also use something like text/template with the template in a single string literal to make it much more readable (and still easily put the contents of your variables into the script).
1

No, there isn't any pretty method for creating .lnk file, in golang.

Primary reason is that, .lnk files are windows specific.

In Windows, even a native program need to use OLE (Object linking and embedding) and COM (component object model) to create a shortcut file, as described in this answer.

In my opinion, One way to approach this problem in golang is to use gowin, and try to communicate with OLE COM.

OR

Write a native windows component that does actual work of creating .lnk file, and just spawn its process through your go program.

3 Comments

Thanks ! I will try it and (obviously) post it on Github if I succeed ! c:
@AlexisPaques Do let me know, when you do that.
First I will just use the external solution, then, maybe, will try to do it in golang
1

Here is an alternative, if for any reason you don't want to use an external go package. As mentioned by Alexis Paques you can use Powershell to create shortcuts under Windows. The advantage is, that it's already available in virtually all Windows environments. Here is an implementation for creating a shortcut in the shell:startup folder, which will auto start the linked program for the current user on 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 answer is based on this SO answer and this gist.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.