start menu apps

Started by spl, March 22, 2025, 01:33:50 PM

Previous topic - Next topic

spl

Been thinking of coding a reportview list of start menu apps [name] which when clicked click would either launch or ask to launch the .lnk associated with the app name. Code below iterates the .lnk files in both the all users and current user folders - breaks them down into name and link properties and adds them to both a list and a map [the list is just for show, the map is more userful. It also has a filter kludge which anyone who wants to give the code a try, can, well..try. 

But there is one issue. The code does not perform a recursive search. I know WB has a file search Extender circa 2013. I didn't have it with my 2025A version, so downloaded it. Didn't find it that useful. Then noticed .NET has a System.IO.Directory.GetFiles() function, with an overload for recursion that takes parameters (parentDirectory, filetype, 1) where 1 is SearchOption.AllDirectories. I haven't tried this in the CLR, out of caution, and possibly if Tony reads this he can reply if it is even worth it.

Anyhoo, here is the code. I'm very rusty with reportview but will give it a whirl.
filter = ''
;optional
usefilter = @false  ;set to @true and update a filter value
if usefilter then filter = 'Visual'
;=================================
;get startmenu items for all users
Dir = "C:\ProgramData\Microsoft\Windows\Start Menu\Programs\" 
dirchange(Dir)
FileList = FileItemize("*.lnk")
files = "Name":@TAB:"Link":@LF
startMap = MapCreate()
Count = ItemCount(FileList, @TAB) 
For i = 1 To Count
    name =  strreplace(ItemExtract(i, FileList, @TAB),".lnk","")
    link = Dir:ItemExtract(i, FileList, @TAB)
    if (usefilter && strindexnc(name,filter,0,@fwdscan)>0)
       files = files:name:@TAB:link:@LF
       startMap[name]=link
    endif
    if !usefilter
       files = files:name:@TAB:link:@LF
       startMap[name]=link
    endif
Next
;get startmenu items for current user
env = Environment("TEMP")
Dir = ""
for i = 1 to 4
   Dir = Dir:ItemExtract(i,env,"\"):"\"
next
Dir = Dir:"Roaming\Microsoft\Windows\Start Menu\Programs\"
dirchange(Dir)
FileList = FileItemize("*.lnk")
Count = ItemCount(FileList, @TAB) 
For i = 1 To Count
    name =  strreplace(ItemExtract(i, FileList, @TAB),".lnk","")
    link = Dir:ItemExtract(i, FileList, @TAB)
    if (usefilter && strindexnc(name,filter,0,@fwdscan)>0)
       files = files:name:@TAB:link:@LF
       startMap[name]=link
    endif
    if !usefilter
       files = files:name:@TAB:link:@LF
       startMap[name]=link
    endif
Next
dirchange(Dirscript())
;options

;output to file, maybe set delimiter to comma for csv
;fileput("c:\temp\startmenu.txt",files)

;just display as tab-delimited columns
;afile = AskItemList("Select Start Menu Item ", files, @LF, @unsorted, @single, @false)
;if afile <> "" Then Display(2,"Selection",afile)

;display as map keys
;put these in a reportview and execute a selected key value
keys = MapKeysGet(startMap,2,@LF)
akey = AskItemList("Select Start Menu Item ", keys, @LF, @unsorted, @single, @false)
if akey <> "" then Display(4,"Key and Link",akey:@LF:startMap[akey])

Exit
Stan - formerly stanl [ex-Pundit]

spl

I took another look at the search extender having missed 16 as a flag. Got the recursion to work, and really no need to create a reportview as app links can be run from askitemlist().

[EDIT]
On second thought, while the original script I posted would include links for Office apps w/out recursion, the extender, with recursion, doesn't.
Stan - formerly stanl [ex-Pundit]

spl

and here is script using CLR. The GetFiles() function works for the dir as only parameter but fails [for me] with the overload for GetFiles(dir,extension,searchOption). Could gather sub-folders with DirItemize() and loop through calling a function to perform GetFiles() with dir,map as parameters. Or, just stop at the parent dir as it provides a quick display for both all user and current user app names and link.

[script using CLR]
;using CLR to help iterate start menu items
;no error handling at present but code as is should work
ObjectClrOption("useany","System.IO")
dt = ObjectClrNew("System.IO.Directory")
Dir = "C:\ProgramData\Microsoft\Windows\Start Menu\Programs\"

startMap = MapCreate()
recurse = ObjectClrType("System.IO.Directory.SearchOption",1)
files = dt.GetFiles(Dir)

;this will error CLR- Type Name Not found, so cannot get sub-folders
;files = dt.GetFiles(Dir,"lnk",recurse)
foreach f in files
   if strindexnc(f,".lnk",0,@fwdscan)>0
      name =  strreplace(FileBasename(f,0),".lnk","")
      startMap[name]=f
   endif
Next

env = Environment("TEMP")
Dir = ""
for i = 1 to 4
   Dir = Dir:ItemExtract(i,env,"\"):"\"
next
Dir = Dir:"Roaming\Microsoft\Windows\Start Menu\Programs\"

files = dt.GetFiles(Dir)

;this will error CLR- Type Name Not found
;files = dt.GetFiles(Dir,"lnk",recurse)
foreach f in files
   if strindexnc(f,".lnk",0,@fwdscan)>0
      name =  strreplace(FileBasename(f,0),".lnk","")
      startMap[name]=f
   endif
Next

dt=0
keys = MapKeysGet(startMap,2,@LF)
akey = AskItemList("Select Start Menu Item To Run ", keys, @LF, @sorted, @single, @false)
if akey <> ""
   ;so link for selected app
   display(4,akey,startMap[akey])
   ;or to test launching the app]
   ;ShellExecute(startMap[akey], "", "", @NORMAL, "")
else
   Display(2,"Exiting","No Item Selected")
endif

Exit
Stan - formerly stanl [ex-Pundit]

JTaylor

You need to use "*.lnk" since you are wanting a search pattern.

; Using CLR to iterate Start Menu items
ObjectClrOption("useany", "System.IO")
dt = ObjectClrNew("System.IO.Directory")
Dir = "C:\ProgramData\Microsoft\Windows\Start Menu\Programs\"

startMap = MapCreate()
recurse = ObjectClrType("System.IO.SearchOption", 1) ; Ensure correct SearchOption type

files = dt.GetFiles(Dir, "*.lnk", recurse) 

foreach f in files
   if strindexnc(f, ".lnk", 0, @fwdscan) > 0
      name = strreplace(FileBasename(f, 0), ".lnk", "")
      startMap[name] = f
   endif
Next

env = Environment("TEMP")
Dir = ""
for i = 1 to 4
   Dir = Dir:ItemExtract(i, env, "\"):"\"
next
Dir = Dir:"Roaming\Microsoft\Windows\Start Menu\Programs\"

files = dt.GetFiles(Dir, "*.lnk", recurse) 

foreach f in files
   if strindexnc(f, ".lnk", 0, @fwdscan) > 0
      name = strreplace(FileBasename(f, 0), ".lnk", "")
      startMap[name] = f
   endif
Next

dt = 0
keys = MapKeysGet(startMap, 2, @LF)
akey = AskItemList("Select Start Menu Item To Run ", keys, @LF, @sorted, @single, @false)
if akey <> ""
   ; Display the selected link
   display(4, akey, startMap[akey])
   ; Optionally, launch the application
   ; ShellExecute(startMap[akey], "", "", @NORMAL, "")
else
   Display(2, "Exiting", "No Item Selected")
endif

Exit

spl

Quote from: JTaylor on March 23, 2025, 12:37:05 PMYou need to use "*.lnk" since you are wanting a search pattern.

I don't think that is an issue (.lnk vs lnk). Thanks for adjusting the clrtype() for the search option. Although files = dt.GetFiles(Dir,".lnk",recurse) avoids the CLR Type error, the foreach now errors. I placed message("",objecttypeget(files)) before the error, it comes back ARRAY|BSTR. Have to switch to array processing. But trying ArrayItemize() fails because array is empty.
Stan - formerly stanl [ex-Pundit]

spl

Jim was right!

;;using CLR to help iterate start menu items
;Stan Littlefield 3/24/2025
;=====================================================
gosub udfs
IntControl(73,1,0,0,0)
ObjectClrOption("useany", "System.IO")
dt = ObjectClrNew("System.IO.Directory")
Dir = "C:\ProgramData\Microsoft\Windows\Start Menu\Programs\" 
startMap = MapCreate()
recurse = ObjectClrType("System.IO.SearchOption", 1) ; Ensure correct SearchOption type
GetDir(Dir) 
 
env = Environment("TEMP")
Dir = ""
for i = 1 to 4
   Dir = Dir:ItemExtract(i, env, "\"):"\"
next
Dir = Dir:"Roaming\Microsoft\Windows\Start Menu\Programs\"
GetDir(Dir)  
 
dt = 0
keys = MapKeysGet(startMap, 2, @LF)
akey = AskItemList("Select Start Menu Item To Run ", keys, @LF, @sorted, @single, @false)
if akey <> ""
   ; Display the selected link
   display(4, akey, startMap[akey])
   ; Optionally, launch the application
   ; ShellExecute(startMap[akey], "", "", @NORMAL, "")
else
   Display(2, "Exiting", "No Item Selected")
endif
 
Exit
:WBERRORHANDLER
dt = 0
geterror()
Terminate(@TRUE,"Error Encountered",errmsg)
;=====================================================

:udfs
#DefineSubRoutine geterror()
   wberroradditionalinfo = wberrorarray[6]
   lasterr = wberrorarray[0]
   handlerline = wberrorarray[1]
   textstring = wberrorarray[5]
   linenumber = wberrorarray[8]
   errmsg = "Error: ":lasterr:@LF:textstring:@LF:"Line (":linenumber:")":@LF:wberroradditionalinfo
   Return(errmsg)
#EndSubRoutine

#DefineSubRoutine GetDir(folder)
IntControl(73,1,0,0,0)
files = dt.GetFiles(folder, "*.lnk", recurse) 
 
foreach f in files
   if strindexnc(f, ".lnk", 0, @fwdscan) > 0
      name = strreplace(FileBasename(f, 0), ".lnk", "")
      startMap[name] = f
   endif
Next
Return 1
:WBERRORHANDLER
IntControl(73,1,0,0,0)
Return 0
#EndSubRoutine

Return
;=====================================================
Stan - formerly stanl [ex-Pundit]

JTaylor

I am not sure I understand.   I had mentioned that you need to use  *.lnk

not lnk or .lnk


It requires a pattern/wildcard type string.   What you are doing is telling it to look for files named ".lnk", not files with an extension of ".lnk".

Jim


Quote from: spl on March 24, 2025, 02:52:23 AM
Quote from: JTaylor on March 23, 2025, 12:37:05 PMYou need to use "*.lnk" since you are wanting a search pattern.

I don't think that is an issue (.lnk vs lnk). Thanks for adjusting the clrtype() for the search option. Although files = dt.GetFiles(Dir,".lnk",recurse) avoids the CLR Type error, the foreach now errors. I placed message("",objecttypeget(files)) before the error, it comes back ARRAY|BSTR. Have to switch to array processing. But trying ArrayItemize() fails because array is empty.


spl

Quote from: JTaylor on March 24, 2025, 07:12:12 AMI am not sure I understand.   I had mentioned that you need to use  *.lnk

not lnk or .lnk

It requires a pattern/wildcard type string.   What you are doing is telling it to look for files named ".lnk", not files with an extension of ".lnk".


DOIT! You are correct. My bad for trying a DOGE pattern.
Stan - formerly stanl [ex-Pundit]

td

Another take:
AddExtender("wwfaf44i.dll",0,"wwfaf64i.dll") 
strDir = "C:\ProgramData\Microsoft\Windows\Start Menu\Programs\" 
hFaf = fafOpen(strDir, '*.lnk', 1|2|16|128)
i = 0
While @TRUE
     Found=fafFind(hFaf)
     If found=='' Then Break
   name = FileRoot(Found)
   Paths[name] = Found
EndWhile
fafClose(hFaf)

strDir = Environment('HOMEDRIVE'):Environment('HOMEPATH')
strDir :="\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\"
hFaf = fafOpen(strDir, '*.lnk', 1|2|16|128) 
While @TRUE
     Found=fafFind(hFaf)
     If found=='' Then Break
   name = FileRoot(Found)
   Paths[name] = Found
EndWhile
fafClose(hFaf)

baseNames = MapKeysGet(Paths, 2, @LF)
AskItemList("Select Item", baseNames, @LF, @sorted, @single, @false)
; etc...
"No one who sees a peregrine falcon fly can ever forget the beauty and thrill of that flight."
  - Dr. Tom Cade

spl

Quote from: td on March 24, 2025, 02:14:22 PMAnother take:

Nice! Now anyone reading will have 2 options to 'skin the code'. As both use a map, would limit a WB version to when maps were available. As a semi-final for the CLR option, the code below now allows a filter.
;using CLR to help iterate start menu items
;updsted: added capacity to filter outcome
;Stan Littlefield 3/25/2025
;=====================================================
gosub udfs
IntControl(73,1,0,0,0)
ObjectClrOption("useany", "System.IO")
dt = ObjectClrNew("System.IO.Directory")
filter ='Microsoft' ;if set to '' all apps will be output
Dir = "C:\ProgramData\Microsoft\Windows\Start Menu\Programs\" 
startMap = MapCreate()
recurse = ObjectClrType("System.IO.SearchOption", 1) ; Ensure correct SearchOption type
GetDir(Dir,filter)  
Dir = Environment('HOMEDRIVE'):Environment('HOMEPATH')
Dir :="\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\"
GetDir(Dir,filter)   
dt = 0
keys = MapKeysGet(startMap, 2, @LF)
akey = AskItemList("Select Start Menu Item To Run ", keys, @LF, @sorted, @single, @false)
if akey <> ""
   ; Display the selected link
   display(4, akey, startMap[akey])
   ; Optionally, launch the application
   ; ShellExecute(startMap[akey], "", "", @NORMAL, "")
else
   Display(2, "Exiting", "No Item Selected")
endif

Exit
:WBERRORHANDLER
dt = 0
geterror()
Terminate(@TRUE,"Error Encountered",errmsg)
;=====================================================
:udfs
#DefineSubRoutine geterror()
   wberroradditionalinfo = wberrorarray[6]
   lasterr = wberrorarray[0]
   handlerline = wberrorarray[1]
   textstring = wberrorarray[5]
   linenumber = wberrorarray[8]
   errmsg = "Error: ":lasterr:@LF:textstring:@LF:"Line (":linenumber:")":@LF:wberroradditionalinfo
   Return(errmsg)
#EndSubRoutine

#DefineSubRoutine GetDir(folder,filter)
IntControl(73,1,0,0,0)
files = dt.GetFiles(folder, "*.lnk", recurse)  
foreach f in files
   name = strreplace(FileBasename(f, 0), ".lnk", "")
   if strindexnc(name, filter, 0, @fwdscan) <> 0
      startMap[name] = f
   endif
Next
Return 1
:WBERRORHANDLER
IntControl(73,1,0,0,0)
Return 0
#EndSubRoutine

Return
;=====================================================
Stan - formerly stanl [ex-Pundit]

spl

Added prompt for filter, or 0 (zero) for all apps, and selected link placed in clipboard.
;using CLR to help iterate start menu items
;updsted: added capacity to filter outcome
;Stan Littlefield 3/25/2025
;=====================================================
gosub udfs
IntControl(73,1,0,0,0)
ObjectClrOption("useany", "System.IO")
dt = ObjectClrNew("System.IO.Directory")
filter = askline("Start Menu Apps Filter","Enter filter, i.e. 'Microsoft', or 0 [zero] for all","",0)
if filter == "" then Terminate(@TRUE,"Exiting","No Filter selected or Cancel Pressed")
if filter == '0' then filter = ''
Dir = "C:\ProgramData\Microsoft\Windows\Start Menu\Programs\" 
startMap = MapCreate()
recurse = ObjectClrType("System.IO.SearchOption", 1) ; Ensure correct SearchOption type
GetDir(Dir,filter)  
Dir = Environment('HOMEDRIVE'):Environment('HOMEPATH')
Dir :="\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\"
GetDir(Dir,filter)   
dt = 0
keys = MapKeysGet(startMap, 2, @LF)
akey = AskItemList("Select Start Menu Item To Run ", keys, @LF, @sorted, @single, @false)
if akey <> ""
   ; Display the selected link and place in clipboard
   ClipPut(startMap[akey])
   display(4, akey, startMap[akey]:@LF:"Placed in Clipboard")
   ; Optionally, launch the application
   ; ShellExecute(startMap[akey], "", "", @NORMAL, "")
else
   Display(2, "Exiting", "No Item Selected")
endif

Exit
:WBERRORHANDLER
dt = 0
geterror()
Terminate(@TRUE,"Error Encountered",errmsg)
;=====================================================
:udfs
#DefineSubRoutine geterror()
   wberroradditionalinfo = wberrorarray[6]
   lasterr = wberrorarray[0]
   handlerline = wberrorarray[1]
   textstring = wberrorarray[5]
   linenumber = wberrorarray[8]
   errmsg = "Error: ":lasterr:@LF:textstring:@LF:"Line (":linenumber:")":@LF:wberroradditionalinfo
   Return(errmsg)
#EndSubRoutine

#DefineSubRoutine GetDir(folder,filter)
IntControl(73,1,0,0,0)
files = dt.GetFiles(folder, "*.lnk", recurse)  
foreach f in files
   name = strreplace(FileBasename(f, 0), ".lnk", "")
   if strindexnc(name, filter, 0, @fwdscan) <> 0
      startMap[name] = f
   endif
Next
Return 1
:WBERRORHANDLER
IntControl(73,1,0,0,0)
Return 0
#EndSubRoutine

Return
;=====================================================

[EDIT] I was also playing around with a .Net Listview Object in WinForms. A lot slower brining up all or filtered apps [used sql as filter]
Stan - formerly stanl [ex-Pundit]