Thread overview
Create Windows "shortcut" (.lnk) with D?
Mar 06, 2016
岩倉 澪
Mar 06, 2016
BBasile
Mar 06, 2016
岩倉 澪
Mar 06, 2016
John
Mar 06, 2016
岩倉 澪
Mar 07, 2016
Andrea Fontana
March 06, 2016
I'm creating a small installation script in D, but I've been having trouble getting shortcut creation to work! I'm a linux guy, so I don't know much about Windows programming...

Here are the relevant bits of code I have:

import core.sys.windows.basetyps, core.sys.windows.com, core.sys.windows.objbase, core.sys.windows.objidl, core.sys.windows.shlobj, core.sys.windows.windef;
import std.conv, std.exception, std.file, std.format, std.path, std.stdio, std.string, std.utf, std.zip;

//Couldn't find these in core.sys.windows
extern(C) const GUID CLSID_ShellLink     = {0x00021401, 0x0000, 0x0000,
  [0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46]};

extern(C) const IID IID_IShellLinkA      = {0x000214EE, 0x0000, 0x0000,
  [0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46]};

extern(C) const IID IID_IPersistFile     = {0x0000010B, 0x0000, 0x0000,
  [0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46]};

void main()
{
    string programFolder, dataFolder, desktopFolder;
    {
        char[MAX_PATH] _programFolder, _dataFolder, _desktopFolder;
        SHGetFolderPathA(null, CSIDL_PROGRAM_FILESX86, null, 0, _programFolder.ptr);
        SHGetFolderPathA(null, CSIDL_LOCAL_APPDATA, null, 0, _dataFolder.ptr);
        SHGetFolderPathA(null, CSIDL_DESKTOPDIRECTORY, null, 0, _desktopFolder.ptr);
        programFolder = _programFolder.assumeUnique().ptr.fromStringz();
        dataFolder = _dataFolder.assumeUnique().ptr.fromStringz();
        desktopFolder = _desktopFolder.assumeUnique().ptr.fromStringz();
    }
    auto inputFolder = buildNormalizedPath(dataFolder, "Aker/agtoolbox/input");
    auto outputFolder = buildNormalizedPath(dataFolder, "Aker/agtoolbox/output");
    CoInitialize(null);
    scope(exit)
        CoUninitialize();
    IShellLinkA* shellLink;
    IPersistFile* linkFile;
    CoCreateInstance(&CLSID_ShellLink, null, CLSCTX_INPROC_SERVER, &IID_IShellLinkA, cast(void**)&shellLink);
    auto exePath = buildNormalizedPath(programFolder, "Aker/agtoolbox/agtoolbox.exe").toStringz();
    shellLink.SetPath(exePath);
    auto arguments = format("-i %s -o %s", inputFolder, outputFolder).toStringz();
    shellLink.SetArguments(arguments);
    auto workingDirectory = programFolder.toStringz();
    shellLink.SetWorkingDirectory(workingDirectory);
    shellLink.QueryInterface(&IID_IPersistFile, cast(void**)&linkFile);
    auto linkPath = buildNormalizedPath(desktopFolder, "agtoolbox.lnk").toUTF16z();
    linkFile.Save(linkPath, TRUE);
}

I tried sprinkling it with print statements and it crashes on shellLink.setPath(exePath);

This is the full script: https://gist.github.com/miotatsu/1cc55fe29d8a8dcccab5
I found the values for the missing windows api bits from this: http://www.dsource.org/projects/tutorials/wiki/CreateLinkUsingCom

I compile with: dmd agtoolbox-installer.d ole32.lib
I got the ole32.lib from the dmd install

Any help would be highly appreciated as I'm new to Windows programming in D and have no idea what I'm doing wrong!
March 06, 2016
On Sunday, 6 March 2016 at 03:13:23 UTC, 岩倉 澪 wrote:
> I'm creating a small installation script in D, but I've been having trouble getting shortcut creation to work! I'm a linux guy, so I don't know much about Windows programming...
>
> [...]
>
> Any help would be highly appreciated as I'm new to Windows programming in D and have no idea what I'm doing wrong!

If you don't want to mess with the Windows API then you can dynamically create a script (I do this in CE installer):

void createLnk(string exeName, string displayName)
{
    import std.process: environment, executeShell;
    import std.file: remove, exists;
    import std.random: uniform;
    import std.conv: to;

    string vbsName;
    do vbsName = environment.get("TEMP") ~ r"\shcsh" ~ uniform(0,int.max).to!string ~ ".vbs";
    while (vbsName.exists);

    string vbsCode = "
        set WshShell = CreateObject(\"WScript.shell\")
        strDesktop = WshShell.SpecialFolders(\"Desktop\")
        set lnk = WshShell.CreateShortcut(strDesktop + \"\\%s.lnk\")
        lnk.TargetPath = \"%s\"
        lnk.Save
    ";
    File vbs = File(vbsName, "w");
    vbs.writefln(vbsCode, displayName, exeName);
    vbs.close;
    executeShell(vbsName);

    vbsName.remove;
}

March 06, 2016
On Sunday, 6 March 2016 at 05:00:55 UTC, BBasile wrote:
> If you don't want to mess with the Windows API then you can dynamically create a script (I do this in CE installer):

This might be an option but I'd prefer to use the Windows API directly. I don't know vb script and maintaining such a script inline would just add cognitive overhead I think.

If I can't get it working with the Windows API; I'll probably have to do it this way though. Thanks for the suggestion!


March 06, 2016
On Sunday, 6 March 2016 at 03:13:23 UTC, 岩倉 澪 wrote:
>     IShellLinkA* shellLink;
>     IPersistFile* linkFile;
>
> Any help would be highly appreciated as I'm new to Windows programming in D and have no idea what I'm doing wrong!

In D, interfaces are references, so it should be:

  IShellLinkA shellLink;
  IPersistFile linkFile;



March 06, 2016
On Sunday, 6 March 2016 at 11:00:35 UTC, John wrote:
> On Sunday, 6 March 2016 at 03:13:23 UTC, 岩倉 澪 wrote:
>>     IShellLinkA* shellLink;
>>     IPersistFile* linkFile;
>>
>> Any help would be highly appreciated as I'm new to Windows programming in D and have no idea what I'm doing wrong!
>
> In D, interfaces are references, so it should be:
>
>   IShellLinkA shellLink;
>   IPersistFile linkFile;

That's exactly what the problem was, thank you!!
March 07, 2016
On Sunday, 6 March 2016 at 20:13:39 UTC, 岩倉 澪 wrote:
> On Sunday, 6 March 2016 at 11:00:35 UTC, John wrote:
>> On Sunday, 6 March 2016 at 03:13:23 UTC, 岩倉 澪 wrote:
>>>     IShellLinkA* shellLink;
>>>     IPersistFile* linkFile;
>>>
>>> Any help would be highly appreciated as I'm new to Windows programming in D and have no idea what I'm doing wrong!
>>
>> In D, interfaces are references, so it should be:
>>
>>   IShellLinkA shellLink;
>>   IPersistFile linkFile;
>
> That's exactly what the problem was, thank you!!

You can use official specs to recreate binary file:
https://msdn.microsoft.com/en-us/library/dd871305.aspx

Ok, not the fastest way. :)