Get shortcut target without evaluating environment variables

Get help with using AutoHotkey and its commands and hotkeys
marin87
Posts: 7
Joined: 17 Sep 2016, 23:55

Get shortcut target without evaluating environment variables

21 Jul 2019, 10:36

Hello,

I'm working on a script which replace shortcut targets with the corresponding environment variables, for a large number of files.
But once the replacement is done, FileGetShortcut returns a target path with evaluated environment variables, so the script will process the same files again and again. (However, this is oddly not the case with the startup directory).

Here is an example with a shortcut that already has an environment variable in its target path:

#NoEnv
shortcutPath := "C:\ProgramData\Microsoft\Windows\Start Menu\Programs\Immersive Control Panel.lnk"
FileGetShortcut, %shortcutPath%, shortcutTargetPath, shortcutStartupPath
MsgBox, shortcutTargetPath: %shortcutTargetPath%`nshortcutStartupPath: %shortcutStartupPath%


Returns:
shortcutTargetPath: C:\Windows\System32\Control.exe
shortcutStartupPath: %HOMEDRIVE%%HOMEPATH%


Expected:
shortcutTargetPath: %windir%\System32\Control.exe
shortcutStartupPath: %HOMEDRIVE%%HOMEPATH%


I also tried to open file properties and get "Edit" controls texts, but it's way too slow for a large number of files.

So is there another way to retrieve the target path "literaly"?
Thanks!
User avatar
jeeswg
Posts: 6904
Joined: 19 Dec 2016, 01:58
Location: UK

Re: Get shortcut target without evaluating environment variables

21 Jul 2019, 12:44

I have a function based on the code here, it returns paths without expanding the environment variables:
COM QueryInterface - Page 3 - Ask for Help - AutoHotkey Community
https://autohotkey.com/board/topic/74471-com-queryinterface/page-3

Code: Select all

q:: ;test shortcuts
;MsgBox, % EnvGet("HOMEDRIVE") "`r`n" EnvGet("HOMEPATH")

vPathLnk := A_Desktop "\New Text Document.lnk"
;vDesktop := A_Desktop
vDesktop := "%HOMEDRIVE%%HOMEPATH%\Desktop"
vTarget := vDesktop "\New Text Document.txt"
vWorkingDir := vDesktop

FileCreateShortcut, % vTarget, % vPathLnk, % vWorkingDir

FileGetShortcut, % vPathLnk, vTarget, vWorkingDir
MsgBox, % Format("{}`r`n{}`r`n{}", vPathLnk, vTarget, vWorkingDir)

JEE_FileGetShortcut(vPathLnk, vTarget, vWorkingDir)
MsgBox, % Format("{}`r`n{}`r`n{}", vPathLnk, vTarget, vWorkingDir)
return

;==================================================

;FileGetShortcut output:
;C:\Users\me\Desktop\New Text Document.lnk
;C:\Users\me\Desktop\New Text Document.txt
;%HOMEDRIVE%%HOMEPATH%\Desktop

;JEE_FileGetShortcut output:
;C:\Users\me\Desktop\New Text Document.lnk
;%HOMEDRIVE%%HOMEPATH%\Desktop\New Text Document.txt
;%HOMEDRIVE%%HOMEPATH%\Desktop

;==================================================

;w:: ;test expand environment variables
vTarget := "C:\Users\%username%\Desktop"
vChars := 20000
VarSetCapacity(vTarget2, vChars*2)
DllCall("kernel32\ExpandEnvironmentStrings", "Str",vTarget, "Str",vTarget2, "UInt",vChars, "UInt")
MsgBox, % vTarget2
return

;==================================================

;based on IShellLinkResolve() by Guests
;COM QueryInterface - Page 3 - Ask for Help - AutoHotkey Community
;https://autohotkey.com/board/topic/74471-com-queryinterface/page-3#entry475169

;get shortcut info
;icon number here is 0-based, AutoHotkey command icon number is 1-based
;unlike AutoHotkey command, this also retrieves the shortcut key
;e.g. FileGetShortcut, % vPath, vTarget, vWorkingDir, vArgs, vDescription, vIconFile, vIconNum, vRunState
;e.g. JEE_FileGetShortcut(vPath, vTarget, vWorkingDir, vArgs, vDescription, vIconFile, vIconNum, vRunState, vShortcutKey)
;JEE_FileLnkGetInfo
;JEE_FileLnkGetDetails
JEE_FileGetShortcut(vPath, ByRef vTarget:="", ByRef vWorkingDir:="", ByRef vArgs:="", ByRef vDescription:="", ByRef vIconFile:="", ByRef vIconNum:="", ByRef vRunState:="", ByRef vShortcutKey:="")
{
	local
	static IID_IShellLinkW := "{000214F9-0000-0000-C000-000000000046}"
	static IID_IShellLinkA := "{000214EE-0000-0000-C000-000000000046}"
	static IID_IShellLink := A_IsUnicode ? IID_IShellLinkW : IID_IShellLinkA
	static CLSID_ShellLink := "{00021401-0000-0000-C000-000000000046}"
	static IID_IPersistFile := "{0000010b-0000-0000-C000-000000000046}"
	if !(pSL := ComObjCreate(CLSID_ShellLink, IID_IShellLink))
		return 0
	if !(pPF := ComObjQuery(pSL, IID_IPersistFile))
		return 0
	;STGM_READ := 0x0
	if DllCall(NumGet(NumGet(pPF+0)+5*A_PtrSize), "Ptr",pPF, "WStr",vPath, "UInt",0x0) ;IPersistFile::Load
		return 0
	;SLR_NO_UI := 0x1
	if DllCall(NumGet(NumGet(pSL+0)+19*A_PtrSize), "Ptr",pSL, "Ptr",A_ScriptHwnd, "UInt",0x1) ;IShellLink::Resolve
		return 0

	;get Target
	;SLGP_RAWPATH := 0x4 ;SLGP_UNCPRIORITY := 0x2 ;SLGP_SHORTPATH := 0x1 ;source: ShObjIdl.h
	VarSetCapacity(WIN32_FIND_DATA, 592)
	VarSetCapacity(vTarget, 260*2)
	if DllCall(NumGet(NumGet(pSL+0)+3*A_PtrSize), "Ptr",pSL, "Str",vTarget, "Int",260, "Ptr",&WIN32_FIND_DATA, "UInt",0x4) ;IShellLink::GetPath
		return 0

	;get WorkingDir
	VarSetCapacity(vWorkingDir, 260*2)
	if DllCall(NumGet(NumGet(pSL+0)+8*A_PtrSize), "Ptr",pSL, "Str",vWorkingDir, "Int",260) ;IShellLink::GetWorkingDirectory
		return 0

	;get Args
	VarSetCapacity(vArgs, 260*2)
	if DllCall(NumGet(NumGet(pSL+0)+10*A_PtrSize), "Ptr",pSL, "Str",vArgs, "Int",260) ;IShellLink::GetArguments
		return 0

	;get Description
	VarSetCapacity(vDescription, 260*2)
	if DllCall(NumGet(NumGet(pSL+0)+6*A_PtrSize), "Ptr",pSL, "Str",vDescription, "Int",260) ;IShellLink::GetDescription
		return 0

	;get IconFile and IconNum
	VarSetCapacity(vIconFile, 260*2)
	VarSetCapacity(vIconNum, 4)
	if DllCall(NumGet(NumGet(pSL+0)+16*A_PtrSize), "Ptr",pSL, "Str",vIconFile, "Int",260, "Ptr",&vIconNum) ;IShellLink::GetIconLocation
		return 0
	vIconNum := NumGet(vIconNum, 0, "Int")

	;get RunState
	VarSetCapacity(vRunState, 4)
	if DllCall(NumGet(NumGet(pSL+0)+14*A_PtrSize), "Ptr",pSL, "Ptr",&vRunState) ;IShellLink::GetShowCmd
		return 0
	vRunState := NumGet(vRunState, "Int")

	;get ShortcutKey
	VarSetCapacity(vShortcutKey, 2)
	if DllCall(NumGet(NumGet(pSL+0)+12*A_PtrSize), "Ptr",pSL, "Ptr",&vShortcutKey) ;IShellLink::GetHotkey
		return 0
	vLoByte := NumGet(vShortcutKey, 0, "UChar")
	vHiByte := NumGet(vShortcutKey, 1, "UChar")
	vShortcutKey := ""
	if (vHiByte & 0x2) ;HOTKEYF_CONTROL := 0x2 ;source: CommCtrl.h
		vShortcutKey .= "^"
	if (vHiByte & 0x1) ;HOTKEYF_SHIFT := 0x1
		vShortcutKey .= "+"
	if (vHiByte & 0x4) ;HOTKEYF_ALT := 0x4
		vShortcutKey .= "!"
	vShortcutKey .= GetKeyName(Format("vk{:x}", vLoByte))

	ObjRelease(pPF)
	ObjRelease(pSL)
	return 1
}
So FileCreateShortcut appears to be working correctly.
And FileGetShortcut is using an equivalent to ExpandEnvironmentStrings: SLGP_RAWPATH to maintain environment variables, off to expand environment variables.

The source code uses SLGP_UNCPRIORITY (but not SLGP_RAWPATH):
script_autoit.cpp
psl->GetPath(buf, MAX_PATH, NULL, SLGP_UNCPRIORITY);

The function above uses SLGP_RAWPATH.

Link:
IShellLinkA::GetPath (shobjidl_core.h) | Microsoft Docs
https://docs.microsoft.com/en-us/windows/win32/api/shobjidl_core/nf-shobjidl_core-ishelllinka-getpath
Last edited by jeeswg on 03 Dec 2019, 04:06, edited 3 times in total.
homepage | tutorials | wish list | fun threads | donate
WARNING: copy your posts/messages before hitting Submit as you may lose them due to CAPTCHA
marin87
Posts: 7
Joined: 17 Sep 2016, 23:55

Re: Get shortcut target without evaluating environment variables

22 Jul 2019, 07:56

Thank you very much for your code, it works well! :D

To edit shortcuts, I use FileCreateShortcut, but as hotkeys are partially supported, is there any code which call the IShellLink::SetHotkey function?
(And I was wondering how do you define which address corresponds to which IShellLink function?)

Thanks!
User avatar
jeeswg
Posts: 6904
Joined: 19 Dec 2016, 01:58
Location: UK

Re: Get shortcut target without evaluating environment variables

22 Jul 2019, 20:21

AHK's FileCreateShortcut has a ShortcutKey parameter.
AHK's FileGetShortcut *does not* have a ShortcutKey parameter.
My FileGetShortcut variant above has a ShortcutKey parameter.

This is where AutoHotkey sets the shortcut key:
script_autoit.cpp
psl->SetHotkey( (WORD)vk | ((WORD)(HOTKEYF_CONTROL | HOTKEYF_ALT) << 8) );

For the methods, I determined the addresses by searching for IShellLinkWVtbl in .h files in:
C:\Program Files (x86)\Windows Kits\8.1
a folder that came with Visual Studio Express for Windows Desktop.
There may be other similar folders, for other Windows versions, depending on your version of Visual Studio.

Info for the methods is under IShellLinkWVtbl in ShObjIdl.h:

Code: Select all

0 QueryInterface
1 AddRef
2 Release
3 GetPath
4 GetIDList
5 SetIDList
6 GetDescription
7 SetDescription
8 GetWorkingDirectory
9 SetWorkingDirectory
10 GetArguments
11 SetArguments
12 GetHotkey
13 SetHotkey
14 GetShowCmd
15 SetShowCmd
16 GetIconLocation
17 SetIconLocation
18 SetRelativePath
19 Resolve
20 SetPath
homepage | tutorials | wish list | fun threads | donate
WARNING: copy your posts/messages before hitting Submit as you may lose them due to CAPTCHA
marin87
Posts: 7
Joined: 17 Sep 2016, 23:55

Re: Get shortcut target without evaluating environment variables

23 Jul 2019, 04:25

The problem is that FileCreateShortcut only supports "Ctrl+Alt" hotkeys combinaisons in its key parameter. I would like to have a SetHotkey function that supports all modifiers, as in the IShellLink::GetHotKey function. This is why I was wondering if there is an AutoHotkey DllCall for IShellLink::SetHotkey.

Thanks for the explanation!
User avatar
jeeswg
Posts: 6904
Joined: 19 Dec 2016, 01:58
Location: UK

Re: Get shortcut target without evaluating environment variables

03 Aug 2019, 16:17

- Oh dear, as you say, FileCreateShortcut only supports Ctrl+Alt hotkeys.
FileCreateShortcut - Syntax & Usage | AutoHotkey
https://www.autohotkey.com/docs/commands/FileCreateShortcut.htm
- It may be a remnant of the old AutoIt code.

- I've basically recreated FileCreateShortcut below, allowing you to choose your own hotkey.
- I based it on script_autoit.cpp in the AHK source code.

Code: Select all

q:: ;test create shortcut
vTarget := A_Desktop "\command-line parameters.ahk"
vOutput = MsgBox, `% DllCall("kernel32\GetCommandLine", "Str")
if !FileExist(vTarget)
	FileAppend, % vOutput "`r`n", % "*" vTarget, UTF-8

;vTarget := A_Desktop "\New Text Document.txt"
vPathLink := A_Desktop "\z link " A_Now ".lnk"
vWorkingDir := A_ScriptDir
vArgs := "a b c"
;vArgs := ""
vDescription := "I am the description"
vIconFile := A_AhkPath
vShortcutKey := "^!+q"
vIconNum := 4
vRunState := 7

;RunState:
;FileCreateShortcut - Syntax & Usage | AutoHotkey
;https://www.autohotkey.com/docs/commands/FileCreateShortcut.htm
;1 - Normal (this is the default)
;3 - Maximized
;7 - Minimized

JEE_FileCreateShortcut(vTarget, vPathLink, vWorkingDir, vArgs, vDescription, vIconFile, vShortcutKey, vIconNum, vRunState)
MsgBox, % "done"
return

;==================================================

JEE_FileCreateShortcut(vTarget, vPathLink, vWorkingDir:="", vArgs:="", vDescription:="", vIconFile:="", vShortcutKey:="", vIconNum:=0, vRunState:="")
{
	local
	static IID_IShellLinkW := "{000214F9-0000-0000-C000-000000000046}"
	static CLSID_ShellLink := "{00021401-0000-0000-C000-000000000046}"
	static IID_IPersistFile := "{0000010b-0000-0000-C000-000000000046}"
	if !(pSL := ComObjCreate(CLSID_ShellLink, IID_IShellLinkW))
		return 0

	;set Target
	;SLGP_RAWPATH := 0x4 ;SLGP_UNCPRIORITY := 0x2 ;SLGP_SHORTPATH := 0x1 ;source: ShObjIdl.h
	if DllCall(NumGet(NumGet(pSL+0)+20*A_PtrSize), "Ptr",pSL, "WStr",vTarget) ;IShellLink::SetPath
		return 0

	;set WorkingDir
	if !(vWorkingDir = "")
	&& DllCall(NumGet(NumGet(pSL+0)+9*A_PtrSize), "Ptr",pSL, "WStr",vWorkingDir) ;IShellLink::SetWorkingDirectory
		return 0

	;set Args
	if !(vArgs = "")
	&& DllCall(NumGet(NumGet(pSL+0)+11*A_PtrSize), "Ptr",pSL, "WStr",vArgs) ;IShellLink::SetArguments
		return 0

	;set Description
	if !(vDescription = "")
	&& DllCall(NumGet(NumGet(pSL+0)+7*A_PtrSize), "Ptr",pSL, "WStr",vDescription) ;IShellLink::SetDescription
		return 0

	;set IconFile and IconNum
	if !(vIconFile = "")
	&& DllCall(NumGet(NumGet(pSL+0)+17*A_PtrSize), "Ptr",pSL, "WStr",vIconFile, "Int",vIconNum) ;IShellLink::SetIconLocation
		return 0

	;set ShortcutKey
	if !(vShortcutKey = "")
	{
		vHotkeyNum := 0
		if RegExMatch(vShortcutKey, "^.{0,2}\+.")
		{
			vHotkeyNum |= (0x1 << 8) ;HOTKEYF_SHIFT := 0x1
			vShortcutKey := StrReplace(vShortcutKey, "+", "",, 1)
		}
		if RegExMatch(vShortcutKey, "^.{0,2}\^.")
		{
			vHotkeyNum |= (0x2 << 8) ;HOTKEYF_CONTROL := 0x2
			vShortcutKey := StrReplace(vShortcutKey, "^", "",, 1)
		}
		if RegExMatch(vShortcutKey, "^.{0,2}!.")
		{
			vHotkeyNum |= (0x4 << 8) ;HOTKEYF_ALT := 0x4
			vShortcutKey := StrReplace(vShortcutKey, "!", "",, 1)
		}
		if vVK := GetKeyVK(vShortcutKey)
			vHotkeyNum |= vVK
		if vVK
		&& DllCall(NumGet(NumGet(pSL+0)+13*A_PtrSize), "Ptr",pSL, "UShort",vHotkeyNum) ;IShellLink::SetHotkey
			return 0
	}

	if !(vRunState = "")
	&& DllCall(NumGet(NumGet(pSL+0)+15*A_PtrSize), "Ptr",pSL, "Int",vRunState) ;IShellLink::SetShowCmd
		return 0

	if !(pPF := ComObjQuery(pSL, IID_IPersistFile))
		return 0
	vSize := DllCall("kernel32\GetFullPathNameW", "WStr","\\?\" vPathLink, "UInt",0, "Ptr",0, "Ptr*",0, "UInt")
	VarSetCapacity(vPath2, vSize*2, 0)
	if !DllCall("kernel32\GetFullPathNameW", "WStr","\\?\" vPathLink, "UInt",vSize, "Ptr",&vPath2, "Ptr*",0, "UInt")
		return 0
	if DllCall(NumGet(NumGet(pPF+0)+6*A_PtrSize), "Ptr",pPF, "Ptr",&vPath2, "Int",1) ;IPersistFile::Save
		return 0

	ObjRelease(pPF)
	ObjRelease(pSL)
	return 1
}

;==================================================
- According to the .lnk file properties, RunState and ShortcutKey are being correctly set, but I'm not sure of a good piece of software to test RunState, and how exactly ShortcutKey works.
- While this link mentions Desktop/the Start Menu, there may be other nuances.
FileCreateShortcut - Syntax & Usage | AutoHotkey
https://www.autohotkey.com/docs/commands/FileCreateShortcut.htm
The ShortcutKey of a newly created shortcut will have no effect unless the shortcut file resides on the desktop or somewhere in the Start Menu. If the ShortcutKey you choose is already in use, your new shortcut takes precedence.
- Btw I corrected GetIconLocation in an earlier post from Int to Ptr.

- Links:
IShellLinkW (shobjidl_core.h) | Microsoft Docs
https://docs.microsoft.com/en-us/windows/win32/api/shobjidl_core/nn-shobjidl_core-ishelllinkw
IPersistFile (objidl.h) | Microsoft Docs
https://docs.microsoft.com/en-us/windows/win32/api/objidl/nn-objidl-ipersistfile
Last edited by jeeswg on 03 Dec 2019, 15:19, edited 5 times in total.
homepage | tutorials | wish list | fun threads | donate
WARNING: copy your posts/messages before hitting Submit as you may lose them due to CAPTCHA
marin87
Posts: 7
Joined: 17 Sep 2016, 23:55

Re: Get shortcut target without evaluating environment variables

09 Sep 2019, 11:02

Thank you very much for your code and explanations, it seems to work as expected! :D
User avatar
jeeswg
Posts: 6904
Joined: 19 Dec 2016, 01:58
Location: UK

Re: Get shortcut target without evaluating environment variables

14 Sep 2019, 15:15

That's great!

I've updated JEE_FileGetShortcut and JEE_FileCreateShortcut above, so they should work with both Unicode and ANSI builds of AHK.
homepage | tutorials | wish list | fun threads | donate
WARNING: copy your posts/messages before hitting Submit as you may lose them due to CAPTCHA
User avatar
jeeswg
Posts: 6904
Joined: 19 Dec 2016, 01:58
Location: UK

Re: Get shortcut target without evaluating environment variables

03 Dec 2019, 04:26

I updated the 2 functions, here are links to the right posts:

JEE_FileGetShortcut:
https://autohotkey.com/boards/viewtopic.php?f=5&t=66420&p=285401#p285401

JEE_FileCreateShortcut:
https://autohotkey.com/boards/viewtopic.php?f=5&t=66420&p=287004#p287004

I fixed 3 RegEx lines, from .? to .{0,2}
homepage | tutorials | wish list | fun threads | donate
WARNING: copy your posts/messages before hitting Submit as you may lose them due to CAPTCHA

Return to “Ask For Help”

Who is online

Users browsing this forum: Albireo, Google [Bot], Odlanir, pn4265 and 246 guests