[VB6, Vista+] Finding and deleting invalid shortcuts with IShellLink and IShellItem
Dead Link Cleaner
So I went looking for a utility to do this for me, and couldn't find one that either itself or its installer didn't look shady/spammy. Since shell interfaces are my favorite thing anyway, I went ahead and wrote program to make it.
I'm posting here instead of utilities because this example marks the first VB6 a technique for enumerating/searching files recursively using the standard IShellItem interface, where most previous examples either aren't recursive, aren't for general file system locations, or use a different method- sticking with IShellItem increases your coding efficiency since you don't need to convert between different ways of interacting with the file system.
Unicode is fully supported. The textbox and listbox are the standard VB controls so won't display Unicode, but the names are stored internally so everything will work; just if you need to use a path with Unicode extended characters in the name, select it with the Browse... button.
Requirements
-Windows Vista or newer (the link check/delete works on XP but the file enumeration uses IEnumShellItems, which is only available as of Vista)
-oleexp.tlb v4.0 or newer
-oleexp addon mIID.bas (included in oleexp download)
Code The code here is to show core concepts, see the full project in the attachment for additional declares and support functions that the below requires to run.
We use IShellLinkW and IPersistFile to load links and grab their target:
Code:
Public Function GetLinkTarget(sLNK As String, Optional bResolve As Boolean = False) As String
Dim pSL As ShellLinkW
Dim ipf As IPersistFile
Dim sTar As String
Dim wfd As WIN32_FIND_DATAW
On Error GoTo e0
Set pSL = New ShellLinkW
Set ipf = pSL
ipf.Load sLNK, STGM_READ
If bResolve Then
pSL.Resolve 0, SLR_UPDATE Or SLR_NO_UI
End If
sTar = String$(MAX_PATH, 0)
pSL.GetPath sTar, MAX_PATH, wfd, SLGP_UNCPRIORITY
pSL.Release
If InStr(sTar, vbNullChar) > 2 Then
sTar = Left$(sTar, InStr(sTar, vbNullChar) - 1)
End If
If Left$(sTar, 1) = vbNullChar Then
GetLinkTarget = ""
Else
GetLinkTarget = sTar
End If
Exit Function
e0:
Debug.Print "GetLinkTarget.Error->" & Err.Description & "(" & Err.Number & ")"
End Function
And the new recursive scanning with only IShellItem and IEnumShellItems is done like this:
Code:
Private Sub Command2_Click()
Dim psi As IShellItem
Dim piesi As IEnumShellItems
Dim isia As IShellItemArray
Dim pidl As Long
Dim pFile As IShellItem
Dim lpName As Long, lpFolder As Long
Dim sName As String, sFolder As String
Dim sDisp As String
Dim pcl As Long
Dim sTarget As String
Dim sStart As String
Dim lAtr As SFGAO_Flags
List1.Clear
ReDim arToDel(0)
nToDel = 0
nLinks = 0
bRslv = (Check3.Value = vbChecked)
pidl = ILCreateFromPathW(StrPtr(sRoot))
SHCreateItemFromIDList pidl, IID_IShellItem, psi
psi.BindToHandler 0&, BHID_EnumItems, IID_IEnumShellItems, piesi
Do While piesi.Next(1&, pFile, pcl) = S_OK
pFile.GetDisplayName SIGDN_DESKTOPABSOLUTEPARSING, lpFolder
Debug.Print "Obj " & LPWSTRtoStr(lpFolder)
pFile.GetAttributes SFGAO_FOLDER Or SFGAO_DROPTARGET Or SFGAO_STREAM, lAtr
If (lAtr And SFGAO_STREAM) = 0 Then
If (lAtr And SFGAO_FOLDER) = SFGAO_FOLDER Then
If (lAtr And SFGAO_DROPTARGET) = SFGAO_DROPTARGET Then
If Check1.Value = vbChecked Then
Debug.Print "FOLDER MATCH"
ScanDeep pFile
End If
End If
Else
pFile.GetDisplayName SIGDN_DESKTOPABSOLUTEPARSING, lpName
sName = LPWSTRtoStr(lpName)
sDisp = Right(sName, Len(sName) - InStrRev(sName, "\"))
If Right$(sName, 4) = ".lnk" Then
Debug.Print "Found link: " & sName
nLinks = nLinks + 1
sTarget = GetLinkTarget(sName, bRslv)
Debug.Print "Link Targt: " & sTarget
If PathFileExistsW(StrPtr(sTarget)) Then
' Debug.Print "Link is valid, skipping."
Else
Debug.Print "Link is invalid, deleting..."
ReDim Preserve arToDel(nToDel)
arToDel(nToDel) = sName
nToDel = nToDel + 1
List1.AddItem sDisp
End If
End If
End If
End If
Loop
Label2.Caption = "Checked " & nLinks & " links total, " & nToDel & " no longer valid and pending deletion."
Call CoTaskMemFree(pidl)
End Sub
Private Sub ScanDeep(psiLoc As IShellItem)
'for recursive scan
Dim psi As IShellItem
Dim piesi As IEnumShellItems
Dim pFile As IShellItem
Dim lpName As Long
Dim sName As String
Dim sDisp As String
Dim pcl As Long
Dim sTarget As String
Dim lAtr As SFGAO_Flags
psiLoc.BindToHandler 0&, BHID_EnumItems, IID_IEnumShellItems, piesi
Do While piesi.Next(1&, pFile, pcl) = S_OK
pFile.GetAttributes SFGAO_FOLDER Or SFGAO_DROPTARGET Or SFGAO_STREAM, lAtr
If (lAtr And SFGAO_STREAM) = 0 Then
If (lAtr And SFGAO_FOLDER) = SFGAO_FOLDER Then
If (lAtr And SFGAO_DROPTARGET) = SFGAO_DROPTARGET Then
ScanDeep pFile
End If
End If
Else
pFile.GetDisplayName SIGDN_DESKTOPABSOLUTEPARSING, lpName
sName = LPWSTRtoStr(lpName)
sDisp = Right(sName, Len(sName) - InStrRev(sName, "\"))
If Right$(sName, 4) = ".lnk" Then
Debug.Print "Found link: " & sName
nLinks = nLinks + 1
sTarget = GetLinkTarget(sName, bRslv)
If PathFileExistsW(StrPtr(sTarget)) Then
' Debug.Print "Link is valid, skipping."
Else
Debug.Print "Link is invalid, deleting..."
ReDim Preserve arToDel(nToDel)
arToDel(nToDel) = sName
nToDel = nToDel + 1
List1.AddItem sDisp
End If
End If
End If
Loop
End Sub
UPDATE - 2017 Jun 15
Project updated to Version 2 with the following changes:
-Added checks to the recursive search function to avoid enumerating zip/cab files and certain types of other containers
-Added option to call Resolve method to attempt to fix broken links where the target still exists somewhere (this isn't like a full search it only checks certain ways, so it doesn't add any noticeable time to the check). If enabled and successful the link file is updated and is never listed as broken.
-Added error handler in GetLinkTarget in case loading or GetPath fails
Last edited by fafalone; Jun 15th, 2017 at 07:03 AM.
Re: [VB6, Vista+] Finding and deleting invalid shortcuts with IShellLink and IShellIt
Thanks.
For educational purposes, as for me, it was very interesting to see how to work with IDL objects in context of IEnumShellItems interface.
Also, I sure most users will find your application very simple and handy.
Forgive me if I'll be too harsh, because I learn shortcuts related malware for several years in practice and wrote articles about it.
So, I see in advance problems in identifing invalid shortcuts with such code.
Also, I almost sure, you don't plan to improve your code as utility, because it is presented mostly as a snippet (but I can be wrong).
However, I still want to hint at several potential problems with false-positives that may arise.
Some reasons are:
- program is x86, target of shortcuts with some embedded environment variables will be expanded incorrectly (mostly, "%programfiles%").
- there are also rarely shortcuts with like %userprofile%. If you expand such EV via current user it can point to wrong location, because was created by another user.
Most of problems solved in my Lnk checker/ClearLNK, available in signature. However, its source code will be always closed, as its analysis/restoring core is secret.
I'm not trying to hijack topic or make ads.
I just like to say: if anyone want to know more, I can help with:
- how to solve described problems;
- to help understand internal structure of LNK (I made some contributions to unofficial LNK format reference and also have own parser).
- can share good 3-d party parsers, and link to unoff. docs, articles where you can read what info stored inside LNK, it's quite unusual beast.
and so.
@fafalone, the only fast and usefull fix for your code I recommend - is to add error handler in GetLinkTarget(), because ipf.Load will fail and show runtime error on broken (damaged) LNK files.
Also, I see a problem with recursive scanning of IEnumShellItems interface.
Here is a screen of sequence your program tried to look in subfolders when I asked to find lnk-s in "c:\users\alex". See Y:\ (I didn't ask to search Y:\) and "Everywhere-search.ms" (that is actually not a folder, as you can see from CMD listing) ? Very strange behaviour.
Re: [VB6, Vista+] Finding and deleting invalid shortcuts with IShellLink and IShellIt
Of course I always want to improve my code, no matter where it is.
Why would environment variables expand differently between Explorer when you execute it and in this project? What it's looking for are links that if you click in Explorer, return file not found.
If this is a concern, it is possible to get the unexpanded target by dropping down to IShellLinkDataList (the topic of a previous codebank entry of mine) and expand it with a corrected method.
What do you mean secret restoring/analysis code? Like the extra details from IShellLinkDataList or finding a moved but still existing target?
If you've got more detailed info and info not available on the MSDN .lnk file format documentation, I'd absolutely be interested in reading it.
Also I understand the issue with the enumeration... Windows it turns out has a very wide definition of what a "folder" is... there's no doubt that what you're looking at was entered because it legitimately and not by mistake had the SFGAO_FOLDER attribute... I've got other file utilities where I've had to follow up checking several more attributes to avoid walking into zip/cab files and others. In this .search-ms case, I'm pretty certain that search results are stored as shell items (which need not be actual physical files), and the .search-ms is a container that can enumerate those items... similar to .library-ms, a container that you can walk as a folder to examine your Library contents (for example, there's a file called Documents.library-ms, if you enumerate with this method, you'll walk C:\Users\<you>\Documents, as well as any other folder included the library. This is a useful feature, because with any other enumeration method, you couldn't let the user select a top-level library, because it's not a real folder, the user would have to select each member folder separately. Had you started a Windows Search in Y:\?
Very interesting post, followups are absolutely welcome.
Last edited by fafalone; Jun 13th, 2017 at 09:02 PM.
Re: [VB6, Vista+] Finding and deleting invalid shortcuts with IShellLink and IShellIt
Why would environment variables expand differently between Explorer when you execute it and in this project?
Because Explorer is x64 bit application. VB6 projects - x32.
If this is a concern, it is possible to get the unexpanded target by dropping down to IShellLinkDataList (the topic of a previous codebank entry of mine) and expand it with a corrected method.
Yea, I saw it and already tried together with my EnvironW(). It works ok.
What do you mean secret restoring/analysis code? Like the extra details from IShellLinkDataList or finding a moved but still existing target?
That mean a "commercial" secret. There are several techniques, that will be always private, because malware makers can use them against us.
Had you started a Windows Search in Y:\?
Maybe. I attached that folder for you.
If you've got more detailed info and info not available on the MSDN .lnk file format documentation, I'd absolutely be interested in reading it.
The amount of information will depend on how deeply you want to know about this format. Ok, I'll describe briefly.
Consider, that LNK is a combined format, that can contain information which can be accessed only by certain interface, e.g. PropertyStoreDataBlock with serialized data ([MS-OLEPS], [MS-PROPSTORE]).
Anyway, some info can be accesses with manual parsing only.
P.S. I let you know, that .LNK can be MSI installer shortcuts. If you try to get its target by IShellLink interface you'll get .ico file, instead of actual target. To get a real target you have to use MsiGetShortcutTarget.
You can install Safari browser to get such LNK. I can't give you ready lnk for testing, because it refers to installed application (depends on registry data).
There are also several kinds of HTTP-LNK, that IShellLink cannot expand, but you don't need them, because it's not a purpose of your application.
Yet another storage format for LNK that represent Microsoft Games shortcuts. And of course, LNK for Control Panel items.
Re: [VB6, Vista+] Finding and deleting invalid shortcuts with IShellLink and IShellIt
I've got other file utilities where I've had to follow up checking several more attributes to avoid walking into zip/cab files and others. In this .search-ms case, I'm pretty certain that search results are stored as shell items (which need not be actual physical files), and the .search-ms is a container that can enumerate those items... similar to .library-ms, a container that you can walk as a folder to examine your Library contents
I understood that such behaviour can be usefull in some cases, but not in this case of enumeration of particular folder.
You will have to come up with how to omit such "folder-container".
I looked a value of "c:\users\alex\searches" attribute, and it returns "lAtr = 0x20000000" (that is exact a SFGAO_FOLDER constant).
So, it looks more difficult that just check the attribute.
P.S. I personally, think that checkbox "Delete Read only LNKs" is useless here. Never heard that user specificially set that attribute. Vice versa, usually malware set that attribute.
Also, your application by default is restricted in rights. Half of LNKs in OS stored in c:\users\Public folder that require admin. privileges to delete.
As a remark: you already included mIID.bas in this project, but vbp-file still refers to ..\tl_ole\mIID.bas
Re: [VB6, Vista+] Finding and deleting invalid shortcuts with IShellLink and IShellIt
Well, first of all the Users\.\Searches *is* a standard file folder, so of course there's no way to differentiate that. BUT, the .search-ms objects it contains are another matter:
Did you change the mask? For .GetAttributes SFGAO_FOLDER, lAtr you have to include each SFGAO_ attribute you want to check. My system doesn't have the registry keys they're pointing to so definitely double check since I could only check the file as-is, but here for .search-ms (but not for .zip/cab), you can differentiate by the SFGAO_DROPTARGET attribute. Real folders have it, .search-ms doesn't at least when I tested the files you posted (so .GetAttributes SFGAO_FOLDER Or SFGAO_DROPTARGET, lAtr).
The read-only option was there because I shared a utility I wrote for my own uses and figured why remove it just because most people won't need it. The link folders I was cleaning have backups, I wanted to also clean the backup copy, and they were backed up with a method that marks every files backup copy as read-only. You seem just a wee bit paranoid about .lnk malware lol
For rights, is this something that's not addressed by running the program as admin?
Regarding mIID, unlike a TLB or OCX, there's no way to refer to a BAS generically that I'm aware of. mIID is a common file, not a per-project file, so the way I recommend using it is from a central location that way you don't have a bunch of different copies when a new version comes out. But mIID isn't in the zip...
Might consider sticking it in the system folder with the TLB and making that the standard instructions...
Edit:
Question for you... links were coming back as bad, but the second i interacted with them in the shell, they became good again (the files targeted still existed but had been moved); I presume this is because of the Resolve method, but that's called even from just moving the .lnk file and not executing it?
Edit2:
New version posted that addresses avoiding pseudo-folders and the error handling. Also has optional Resolve method call now.
--
By the way, what I was using this for was to delete links from another IShellLink-related routine I made that takes your movie files, renames them automatically to a standardized format adding the year if missing, downloads a poster, and creates a link with the posters in a central folder, so you can browse your movies like below without having to use a fancy media library program (it gets the poster/year from theMovieDB web API).. if anyone is interested in a utility like that let me know, it's part of a larger program but I could split it off.
Last edited by fafalone; Jun 15th, 2017 at 01:49 AM.
Re: [VB6, Vista+] Finding and deleting invalid shortcuts with IShellLink and IShellIt
Originally Posted by Dragokas
I looked a value of "c:\users\alex\searches" attribute
Sorry, I mean I tested "c:\users\alex\searches\Everywhere.search-ms" and it had attribute 0x20000000, because I didn't read docs about using IShellItem::GetAttributes.
you can differentiate by the SFGAO_DROPTARGET attribute. Real folders have it, .search-ms doesn't at least when I tested the files you posted
I see. Your new version is filter that container now.
You seem just a wee bit paranoid about .lnk malware lol
I don't understand what you mean. Do you not beleive adware set that flag. I can give you several fresh topics where you will find [RO] in logs. Also, 30% topics about infected PC with adware in Russian region has always LNK infections by statistics.
For rights, is this something that's not addressed by running the program as admin?
Also, don't understand what you mean.
Default security settings for c:\users\Default (yes, Default, not Public, it's misprint above post) folder has no ACL for current user (even Admin) with limited token to allow Write/Delete access for files.
So, not elevated program cannot delete lnk-s there.
However, this is just about default security settings. There are also can be altered ACLs for any folder.
Question for you... links were coming back as bad, but the second i interacted with them in the shell, they became good again (the files targeted still existed but had been moved); I presume this is because of the Resolve method, but that's called even from just moving the .lnk file and not executing it?
Yes, ::Resolve.
AFAIK. They are using FILE_ID_DESCRIPTOR to bind to the target, that is not depend on the file location path. However it is more сomplicated mechanism. If first (ID-method) failed, system try to search target (on some OS), based on size/name..., saved inside lnk.
In some cases, method can be automatically called even if you open a folder that contains lnk via Explorer. Also, when you open a properties of lnk. (not always (!)
I don't use this method because it can start a LONG file searching. However, perhaps it's a good idea to manually extract the ID and find target with DeviceIoControl for lnk fixing.
Of couurse, you can use flag SLR_NO_UI with timeout. But, I personally, don't like Windows touch lnk-s in write access without my permission.
All other behaviors you are asking about - I don't remember. You can check it yourself - ::Resolve is not called if you are accessing lnk via IShellLink::GetPath.
(this isn't like a full search it only checks certain ways, so it doesn't add any noticeable time to the check).
It will. In some cases and on some OS. As I said it's better to set timeout.
P.S. In attach - example of Microsoft Games lnk, I mentioned earlier about. Currently it's one of false positive for your program as well as Control Panel lnk-s.
You can read a bit about MS Games lnk struct. and registry location it points to here: http://www.donationcoder.com/forum/i...1836.msg301583
Re: [VB6, Vista+] Finding and deleting invalid shortcuts with IShellLink and IShellIt
Haven't tried game links but my control panel links are filtered out in the new version because they have the SFGAO_STREAM attrib.. is this not the case on your system? Win10?
---
Is FILE_ID_DESCRIPTOR retained when moving from one physical volume to another (both NTFS)?
But even when it was resolving a file that had been permanently deleted, it didn't execute a hard search.
----
Also, don't understand what you mean.
Default security settings for c:\users\Default (yes, Default, not Public, it's misprint above post) folder has no ACL for current user (even Admin) with limited token to allow Write/Delete access for files.
So, not elevated program cannot delete lnk-s there.
However, this is just about default security settings. There are also can be altered ACLs for any folder.
You said they need admin access to delete, I was asking if 'run as administrator' provides the needed permissions.
Advanced security issues like that aren't things I've really looked into to understand... I've been frequently denied permission from my full admin account though, and don't understand how I could possibly get higher priveleges in Windows.
---
I don't understand what you mean. Do you not beleive adware set that flag. I can give you several fresh topics where you will find [RO] in logs. Also, 30% topics about infected PC with adware in Russian region has always LNK infections by statistics.
I'm not saying lnk malware doesn't exist, just that things this thread is about shouldn't really be evoking an association with it... I dunno, sorry, I meant no offense. Your continued sharing of your deep knowledge of the lnk format is very much appreciated.
Re: [VB6, Vista+] Finding and deleting invalid shortcuts with IShellLink and IShellIt
Haven't tried game links but my control panel links are filtered out in the new version because they have the SFGAO_STREAM attrib.. is this not the case on your system? Win10?
Win 7.
1)
It is the case for default (pre-installed) game lnk-s.
But it is not the case for other installed games, like example with "Oblivion The Elder Scrolls IV™.lnk" as I attached above.
Even if you omit such lnk-s, it sounds like selective checking.
I beleive, some day you will found a way (interface or undoc. functions), that OS is really using for expanding target of every type of lnk.
2)
No, some of my Control Panel lnk-s are not filtered. I didn't study the details of why. But, I attached for you all such lnk, including all lnk, that your program shows as "Link is invalid" on my machine that in real is false.
But even when it was resolving a file that had been permanently deleted, it didn't execute a hard search.
You said they need admin access to delete, I was asking if 'run as administrator' provides the needed permissions.
Yes, it is.
I'm not saying lnk malware doesn't exist, just that things this thread is about shouldn't really be evoking an association with it... I dunno, sorry, I meant no offense. Your continued sharing of your deep knowledge of the lnk format is very much appreciated.
I just introduced you to different possible situations. You are right, on infected macines users should use specific tools for cure / deleting lnk-s.