- Matt Newman's tlink32 [1]
- by way of DDE, as David Gravereaux exemplifies:
dde execute progman progman "" "\[CreateGroup(Bogus)\]" dde execute progman progman "" \ "\[AddItem(notepad.exe,BogusPadLink)\]" dde execute progman progman "" "\[ShowGroup(Bogus,0)\]" dde execute progman progman "" "\[ShowGroup(Bogus,1)\]" dde execute progman progman "" "\[DeleteItem(BogusPadLink)\]" dde execute progman progman "" "\[DeleteGroup(Bogus)\]"When I do this, it actually shows a directory explorer window thing on the screen. I don't want that, but rather, to just add a program to the Start->Programs menu... Ideas?
- Steve Cassidy wraps this DDE approach in a progman.tcl [2] package designed "package to allow creation of program groups and shortcuts in the start menu on Windows 9x/NT"
- [Bill Schongar]'s WISE hack [3]
- freeWrap includes "shell link" functionality comparable to tlink32's.
- NT-TCL [4] includes a shortcut.dll that although ancient works with recent version of Tcl thanks to the stubs interface. Can't seem to find the source though.
- Use Tcom (see below)
- TWAPI provides the read_shortcut [5] and write_shortcut [6] commands.
RS tried reading such links, or "shortcuts", in a simpler, and pure-Tcl way. If you look at .lnk files in hexdump, you notice that they contain the string of what they link to, among other things. So I tried just to split on NUL bytes and see whether a snippet is a valid absolute path:
proc readlnk {lnk} { set res "" set fp [open $lnk] foreach snip [split [read $fp] \x00] { if {[regexp {[A-Z]:\\} $snip] && [file exists $snip]} { lappend res $snip } } close $fp join $res }
This is highly experimental, but it worked on the few examples I tried - please correct me if you know better! Simple links to directories and files work. Links that contain an executable invocation with arguments and icon, are a funny mix of ASCII and Unicode, so splitting on 0 is not a good idea there, and the above code does not work.See also Symbolic links in Windows/CE
NJG
package require tcom set sh [::tcom::ref createobject "WScript.Shell"] set lnk [$sh CreateShortcut {D:\WORK\Acrobat.lnk}] $lnk TargetPath {"D:\Program Files\Adobe\Acrobat 4.0\Acrobat\Acrobat.exe"} $lnk WorkingDirectory {D:\WORK} $lnk Arguments Tutorial.pdf $lnk Save
EFAnd to read the content of a link (code inspired by the VBScript example below):
proc readlnk { lnk } { package require tcom if { ![file exists $lnk] } { return -code error "'$lnk' is not an accessible file" } set sh [::tcom::ref createobject "WScript.Shell"] set lnk [$sh CreateShortcut [file nativename $lnk]] return [$lnk TargetPath] }
[BEO] Using Tcom these procedures can create, modify, and get the contents of MS Shortcut files.
package require tcom # # Create a new MS Windows shortcut file. # # Requires MS Windows 2000 or later (shell v5.00+) # # Args: file = Name of shortcut file to create. # other args are: # Arguments "args.." = any cmd lines options # Description "text" = Description of Shortcut # Hotkey "sequence" = Hot key sequence. Must start with CTRL+ALT. Ex. CTRL+ALT+SHIFT+X # IconLocation "filename,number" = Path to icon file and icon # # TargetPath filename = Destination of the shortcut # WindowStyle style = number (1=normal, 3=maximized, 7=minimized) # WorkingDirectory dirname = pathname of working directory # Returns: Boolean result # # Example: create_shortcut wish.lnk Description "Tcl/Tk" WindowStyle 1 \ # TargetPath {C:\Program Files\TCL\bin\wish.exe} # proc create_shortcut {file args} { if {![string match ".lnk" [string tolower [file extension $file]]]} { append file ".lnk" } if {[string match "windows" $::tcl_platform(platform)]} { # Make sure filenames are in nativename format. array set opts $args foreach item [list IconLocation Path WorkingDirectory] { if {[info exists opts($item)]} { set opts($item) [file nativename $opts($item)] } } set oShell [tcom::ref createobject "WScript.Shell"] set oShellLink [$oShell CreateShortcut [file nativename $file]] foreach {opt val} [array get opts] { if {[catch {$oShellLink $opt $val} result]} { return -code error "Invalid shortcut option $opt or value $value: $result" } } $oShellLink Save return 1 } return 0 } # # Modify a MS Windows shortcut file. # # Requires MS Windows 2000 or later (shell v5.00+) # # Args: file = Shortcut filename. # other args are: # Arguments = any cmd lines options # Description = Description of Shortcut # Hotkey = Hot key sequence. Must start with CTRL+ALT. Ex. CTRL+ALT+SHIFT+X # IconLocation = "pathname","icon #" # Path = destination of the shortcut # WindowStyle = number (1=normal, 3=maximized, 7=minimized) # WorkingDirectory = pathname of working directory # Returns: Boolean result # # See: http://www.microsoft.com/technet/scriptcenter/resources/qanda/feb05/hey0209.mspx # http://www.microsoft.com/technet/scriptcenter/guide/sas_wsh_aytf.mspx # http://www.microsoft.com/technet/scriptcenter/resources/qanda/aug05/hey0812.mspx # proc modify_shortcut {file args} { set dir [file nativename [file dirname $file]] set tail [file nativename [file tail $file]] if {![string match ".lnk" [string tolower [file extension $file]]]} { return -code error "$file is not a valid shortcut name" } if {[string match "windows" $::tcl_platform(platform)]} { # Make sure filenames are in nativename format. array set opts $args foreach item [list IconLocation Path WorkingDirectory] { if {[info exists opts($item)]} { set opts($item) [file nativename $opts($item)] } } # Get Shortcut file as an object set oShell [tcom::ref createobject "Shell.Application"] set oFolder [$oShell NameSpace $dir] set oFolderItem [$oFolder ParseName $tail] # If its a shortcut, do modify if {[$oFolderItem IsLink]} { set oShellLink [$oFolderItem GetLink] foreach {opt val} [array get opts] { if {[catch {$oShellLink $opt $val} result]} { return -code error "Invalid shortcut option $opt or value $value: $rsult" } } $oShellLink Save } return 1 } return 0 } # # Get linked to file for MS Windows shortcut or file link. # # Requires MS Windows 2000 or later (shell v5.00+) # # Args: file = Shortcut filename. # Returns: filename shortcut links to. # proc get_shortcut_filename {file} { set dir [file nativename [file dirname $file]] set tail [file nativename [file tail $file]] if {![string match ".lnk" [string tolower [file extension $file]]]} { return -code error "$file is not a valid shortcut name" } if {[string match "windows" $::tcl_platform(platform)]} { # Get Shortcut file as an object set oShell [tcom::ref createobject "Shell.Application"] set oFolder [$oShell NameSpace $dir] set oFolderItem [$oFolder ParseName $tail] # If its a shortcut, do modify if {[$oFolderItem IsLink]} { set oShellLink [$oFolderItem GetLink] return [$oShellLink Path] } else { if {![catch {file readlink $file} new]} { set new } else { set file } } } else { if {![catch {file readlink $file} new]} { set new } else { set file } } } # # Get properties of a MS Windows shortcut file. # # Requires MS Windows 2000 or later (shell v5.00+) # # Args: file = Shortcut filename. # Returns: list of option and value pairs. # # Return Example: Path {C:\Program Files\TCL\bin\test.file} Hotkey 0 # Description {Shortcut to test.file} WorkingDirectory {} # Arguments {} ShowCommand 1 Target ::tcom::handle0x00C2D938 # proc get_shortcut {file} { set dir [file nativename [file dirname $file]] set tail [file nativename [file tail $file]] if {![string match ".lnk" [string tolower [file extension $file]]]} { return -code error "$file is not a valid shortcut name" } if {[string match "windows" $::tcl_platform(platform)]} { # Get Shortcut file as an object set oShell [tcom::ref createobject "Shell.Application"] set oFolder [$oShell NameSpace $dir] set oFolderItem [$oFolder ParseName $tail] # If its a shortcut, get linked to file if {[$oFolderItem IsLink]} { set oShellLink [$oFolderItem GetLink] set if [tcom::info interface $oShellLink] set list [list] foreach entry [$if properties] { foreach {ptr io type name} $entry break if {[catch {lappend list $name [$oShellLink $name]}]} { lappend list $name {} } } set list } } }
AF - here is a code snippet i wrote to parse .lnk files. I lost interest when i found out how complex the format was and that i would never be able to write them.
array set sizeof [list a 1 A 1 b 1 B 1 h 1 H 1 c 1 s 2 S 2 i 4 I 4 w 8 W 8] proc getdword {fh} { binary scan [read $fh 4] i tmp return $tmp } proc getword {fh} { binary scan [read $fh 2] s tmp return $tmp } proc struct {name defs} { global struct_sizes struct_defs set offset 0 set tmp {} foreach {type n} $defs { set type [string trim $type] set n [string trim $n] if {[string match string* $type]} { set len [lindex [split $type {[]}] 1] set type string } else { if {![info exists struct_sizes($type)]} { error "unknown type" } set len $struct_sizes($type) } lappend tmp [list $n $offset $type $len] incr offset $len } set struct_defs($name) [linsert $tmp 0 $offset] } proc _islnk {fh} { fconfigure $fh -encoding binary -translation binary -eofchar {} if {[getdword $fh] != "76"} { close $fh; return -code error "not a lnk file" } binary scan [read $fh 16] h32 tmp if {$tmp != "10412000000000000c00000000000064"} { close $fh; return -code error "unrecognized GUID" } return $fh } proc readlnk {lnk} { array set array {} set fh [_islnk [open $lnk r]] set array(flags) [getdword $fh] set attributes [read $fh 4] struct $fh * array(created) w array(modified) w array(acessed) w array(size) i array(icon) i array(showwnd) i array(hotkey) i res1 i res2 i if {$array(flags) & 1 > 0} { set len [getword $fh] set w1 [walkStart $fh s *] while {![walkDone $w1]} { puts [walkNext $w1] } walkEnd $w1 } set offset [tell $fh] set structlen [getdword $fh] if {$array(flags) & 2 > 0 && $structlen > 0} { struct $fh * nextstruct i flflags i lvt i lvbp i nvt i path i set nextstruct [expr {[tell $fh] + $structlen - $nextstruct}] if {($flflags & 1) > 0} { seek $fh [expr {$offset + $lvt}] start struct $fh [expr {[getdword $fh] - 4}] type i serial i noff i name a* seek $fh [expr {$offset + $lvbp}] start set base [read $fh [expr {$offset + $structlen - [tell $fh]}]] } if {($flflags & 2) > 0} { seek $fh [expr {$offset + $nvt}] start struct $fh [expr {[getdword $fh] - 4}] a i b i c i d i base a* } seek $fh [expr {$offset + $path}] start set path [read $fh [expr {$nextstruct - [tell $fh]}]] seek $fh $nextstruct start } set array(base) [string trimright $base \x00] set array(final) [string trimright $path \x00] set f 4 foreach name "comment relativepath workingdir commandline icon" { if {($array(flags) & $f) > 0} { set len [getword $fh] set array($name) [read $fh [expr {$len * 2}]] } set f [expr {$f * 2}] } return [array get array] } array set blah [readlnk $argv] parray blah if {$blah(final) == ""} { puts "--> $blah(base)" } else { puts "--> $blah(base)\\$blah(final)" }
Francois Vogel, 25/12/06:I found the following link [8] detailing the format of the Windows shortcut. Not straightforward, but the code above is a good starting point candidate for parsing it.
Another simple way, if you have Cygwin (RS):
exec ln -s /path/to/this/file that
LV What does it take to create Start menu entries from a Tcl script? What about putting a specific icon on the Windows desktop that will then launch a specific program?MG Nov 21 2005 - On Win XP, you can get all the info you need from the $env var:
set base $env(USERPROFILE) ;# this user only set base $env(ALLUSERSPROFILE) ;# for all users # Create a folder in Start->Programs to put your shortcuts/files in.. set start_menu [file mkdir [file join $base "Start Menu" "Programs" "MyApp"]] # Get the path of the Desktop to put your files in/on... set desktop [file join $base Desktop]davidw - as far as I can tell the above will only work on English windows installations.
Peter Newman 24 November 2005: The easiest way to read/write *.LNK/*.URL files has to be to use Windows Script Host. As I understand it, this comes bundled with Windows 98 to XP. For other versions of Windows (or to upgrade to the latest version of WSH), you can download the latest version (5.6) (from Microsoft, for free).Basically, WSH is two EXE files:- wscript.exe, which you call from GUI programs, and:- cscript.exe, which you run from the command line. Both run either VBScript or JScript script files. And both VBScript and JScript support a CreateShortcut method/function, which either creates a new, or opens an existing, *.LNK/*.URL file. You can then read or write the following properties:-
Arguments http://msdn.microsoft.com/library/en-us/script56/html/wsproarguments.asp Description http://msdn.microsoft.com/library/en-us/script56/html/wsprodescription.asp FullName http://msdn.microsoft.com/library/en-us/script56/html/wslrffullnamepropertywshshortcutobject.asp Hotkey http://msdn.microsoft.com/library/en-us/script56/html/wsprohotkey.asp IconLocation http://msdn.microsoft.com/library/en-us/script56/html/wsproiconlocation.asp RelativePath http://msdn.microsoft.com/library/en-us/script56/html/wslrfrelativepathproperty.asp TargetPath http://msdn.microsoft.com/library/en-us/script56/html/wsprotargetpath.asp WindowStyle http://msdn.microsoft.com/library/en-us/script56/html/wsprowindowstyle.asp WorkingDirectory http://msdn.microsoft.com/library/en-us/script56/html/wsproworkingdirectory.aspThe following is a VBScript that reads just the TargetPath property, storing it in the one-line text file whose filespec you pass to the script in an environment variable.
' ------------------------------------------------------------------------------ ' GetShortcutTarget.VBS ' VBScript to extract info from a Windows *.LNK (= 'shortcut') file. ' ------------------------------------------------------------------------------ ' ------------------------------------------------------------------------------ ' OVERVIEW! ' --------- ' This routine:- ' ' 1. Opens the Windows shortcut file specified by the:- ' GetShortcutTarget_LinkFileFilespec ' environment variable, and then; ' ' 2. Writes the required shortcut details to the text file specified by the:- ' GetShortcutTarget_DataFileFilespec ' environment variable. ' ' The Link file info. that can be obtained is:- ' Arguments Property ' Description Property ' FullName Property ' Hotkey Property ' IconLocation Property ' RelativePath Property ' TargetPath Property ' WindowStyle Property ' WorkingDirectory Property ' ------------------------------------------------------------------------------ Dim WSHShell Set WSHShell = WScript.CreateObject("WScript.Shell") Set WshSysEnv = WshShell.Environment("PROCESS") 'WScript.Echo "LINK FILE: " & WshSysEnv("GetShortcutTarget_LinkFileFilespec") 'WScript.Echo "DATA FILE: " & WshSysEnv("GetShortcutTarget_DataFileFilespec") Set myShortcut = WSHShell.CreateShortcut(WshSysEnv("GetShortcutTarget_LinkFileFilespec")) 'MyShortcut.TargetPath = WSHShell.ExpandEnvironmentStrings("%windir%\notepad.exe") 'MyShortcut.WorkingDirectory = WSHShell.ExpandEnvironmentStrings("%windir%") 'MyShortcut.WindowStyle = 4 'MyShortcut.IconLocation = WSHShell.ExpandEnvironmentStrings("%windir%\notepad.exe, 0") 'MyShortcut.Save (This is an example of CREATING an *.LNK file) Const ForReading = 1, ForWriting = 2 Dim myFileSystemObject, myFileObject Set myFileSystemObject = CreateObject("Scripting.FileSystemObject") Set myFileObject = myFileSystemObject.OpenTextFile(WshSysEnv("GetShortcutTarget_DataFileFilespec"), ForWriting, True) myFileObject.WriteLine myShortcut.TargetPath myFileObject.CloseI run that script from VB .NET. But, from Tcl you'd go something like (I haven't tested it):-
set env("GetShortcutTarget_LinkFileFilespec") yourLinkFile set env("GetShortcutTarget_DataFileFilespec") yourDataFile # (will be created/overwritten) exec wscript.exe GetShortcutTarget.VBS # if you're using 'wish' # --OR-- exec cscript.exe GetShortcutTarget.VBS # if you're using 'tclsh' # ...some Tcl to read the first line of the output DataFile (which gives you the required TargetPath)...You can easily modify the above to get the other properties too. Note that you don't have to worry about the paths to wscript.exe and cscript.exe. Windows figures this out itself.Creating the Link file is just as easy. See http://msdn.microsoft.com/library/default.asp?url=/library/en-us/script56/html/wsmthcreateshortcut.asp (If that link dies, search for "CreateShortcut" on MSDN.)See also http://msdn.microsoft.com/library/default.asp?url=/library/en-us/script56/html/vtoriMicrosoftWindowsScriptTechnologies.asp (which seems to be the MSDN home page for WSH/VBScript/JScript info).Obviously, you can do the above from any language (not just Tcl). You can also; call COM objects, manipulate network drives and printers, do Office/Word/Excel automation, and quite a lot of other useful things from WSH/VBscript/JScript. And it's quite lightweight and fast too. I doubt you'd chuck Tcl for VBScript or JScript. But, for some Windows specific things (like those listed above), it's usually the easiest and most powerful solution. (And at least you're still scripting.)
[ASH]The example above is very good. Based on this, if you want a program just to echo the target path of the lnk file, then you can do this:
Dim WSHShell Set WSHShell = WScript.CreateObject("WScript.Shell") Set WshSysEnv = WshShell.Environment("PROCESS") Set myShortcut = WSHShell.CreateShortcut(Wscript.Arguments.Item(0)) WScript.Echo myShortcut.TargetPathput this in a .vbs file, let's say lnk2path.vbs, and then run:cscript //nologo lnk2path.vbs xxx.lnkwith xxx.lnk replaced by your actual link and it will output the target path.
Suppose you have to programmatically launch some application using it's shell link, exec obviously doesn't work. Here're the ways to do this:
# Doesn't respect "Run" settings of the shortcut (window state - Normal/Maximized/Minnimized). exec rundll32 shell32.dll,ShellExec_RunDLL $link # Slightly complicated, requires tcom package, but respects "Run" settings. package require tcom set shell [ ::tcom::ref createobject WScript.Shell ] $shell Run $linkRelated information appears in "Windows specific Tcl commands". Microsoft Windows and Tcl - Windows shortcut