Find target for .lnk shortcut in Windows 10 using C# - c#

I wish to find the target of a shortcut (.lnk file, not a symlink) in Windows 10 using C#.
I have searched for hours and found numerous methods for doing this, but two comments I found are memorable, that shortcuts in Windows 10 are somewhat different. The other is that it's trickier than it sounds. None of the methods I have tried work. I've referenced all the necessary COM objects.
They compile (the full programs do) but produce no output, with the exception of the Shell32 idea that has a permission error.
Examples of what I've tried (snippets)
IWshRuntimeLibrary.WshShell shell = new IWshRuntimeLibrary.WshShell();
IWshRuntimeLibrary.IWshShortcut shortcut = (IWshRuntimeLibrary.IWshShortcut)shell.CreateShortcut(filePath);
return shortcut.TargetPath;
// supposed to reference an existing shortcut, but no output
---or---
dynamic shortcut;
dynamic windowsShell;
Type shellObjectType = Type.GetTypeFromProgID("WScript.Shell");
windowsShell = Activator.CreateInstance(shellObjectType);
shortcut = windowsShell.CreateShortcut(LinkName);
string Properfile = shortcut.TargetPath;
// Release the COM objects
shortcut = null;
windowsShell = null;
return Properfile;
//no output
---or---
string pathOnly = System.IO.Path.GetDirectoryName(shortcutFilename);
string filenameOnly = System.IO.Path.GetFileName(shortcutFilename);
Shell shell = new Shell();
Folder folder = shell.NameSpace(pathOnly);
FolderItem folderItem = folder.ParseName(filenameOnly);
if (folderItem != null)
{
Shell32.ShellLinkObject link = (Shell32.ShellLinkObject)folderItem.GetLink;
return link.Path;
}
// permission error
They're snippets but convey the idea of input, procedure and result.
The only other lead I found was to a Microsoft document on the structure of a .lnk file. I've seen solutions that parse them (older versions) but would really like to stay with a modern API.
So I summarised (yep, Aus spelling) what I want, what I've tried and how the code failed.
To put in perspective, the goal is to have a window of shortcuts, but it seems I need to go back to the executable to get different sized icons in a ListView.

This has been answered here by Alexey:
Add Application Manifest File, app.manifest, to your Solution's Startup project if not currently there (Right Click on Project -> Add -> New Item -> General -> Application Manifest File).
In your app.manifest file,
replace this line of code:
<requestedExecutionLevel level="asInvoker" uiAccess="false" />
with this:
<requestedExecutionLevel level="requireAdministrator" uiAccess="false" />
the requireAdministrator makes your app run with Administrator's Right that gives you the required access.

You can try on something like this:
public static void CreateShortcut(string shortcutName, string shortcutPath, string targetFileLocation)
{
string shortcutLocation = System.IO.Path.Combine(shortcutPath, shortcutName + ".lnk");
WshShell shell = new WshShell();
IWshShortcut shortcut = (IWshShortcut)shell.CreateShortcut(shortcutLocation);
shortcut.Description = "Discription"; // The description of the shortcut
shortcut.IconLocation = "Path";// The icon of the shortcut
shortcut.TargetPath = targetFileLocation; // The path of the file that will launch when the shortcut is run
shortcut.Save(); // Save the shortcut
}
And this is how you would use it:
CreateShortcut("Name", PathToDesktop, InstallPathwitheExtention");

Related

How to enumerate the real Windows File Explorer windows

I'm trying to enumerates all open File Explorer windows in a PowerShell script.
I have already found on other posts how to enumerate all explorer.exe windows instances, for example using the Shell.Application COM API:
(New-Object -com "Shell.Application").windows()
But this actually returns more than I want:
I want only the "real" File Explorer windows showing actual files on my disk or network, not the "fake" explorer.exe instances that are just containers for various Control Panel windows, etc.
So basically the list of instances shown when hovering the mouse over the File Explorer icon on the Taskbar.
How can this be done reliably, and preferably in a way that works in Windows 7 to 11?
Comparing the window title to known strings like "Control Panel" or "Windows Update" has limited value. This would only eliminate the most common cases, and on English versions of Windows only.
I tried looking at the File Explorer window class, but it's "CabinetWClass" in all cases, even for Control Panels.
I noticed that real instances have a child window of class "UIRibbonWorkPane", whereas the Control Panel does not. But the ribbon can be disabled, so this is not a reliable marker.
My script already contains C# declarations encapsulating WIN32 API calls, so C# code snippets would also do.
2021-10-10 update:
The best algorithm I've found so far, building on #simon-mourier's answer, can summarized this way:
$self = $window.Document.Folder.Self
$ClassID = $Self.ExtendedProperty("System.NamespaceCLSID")
$BaseClassID = $Self.Path.Substring(2,38) # With proper tests to clear it if it's not a UUID
$FileExplorerIDs = ( # The few known types which are file systems, but don't set $Self.IsFileSystem
# Windows 10
"f02c1a0d-be21-4350-88b0-7367fc96ef3c", # Network
"679f85cb-0220-4080-b29b-5540cc05aab6", # Quick Access
"20d04fe0-3aea-1069-a2d8-08002b30309d", # This PC
# Windows 7
"031e4825-7b94-4dc3-b131-e946b44c8dd5" # Libraries
)
if ($Self.IsFileSystem) {
$AppType = "File Explorer"
} elseif ($FileExplorerIDs -contains "$ClassID") {
$AppType = "File Explorer"
} elseif ($BaseClassID -eq "{26EE0668-A00A-44D7-9371-BEB064C98683}") {
$AppType = "Control Panel"
} elseif ("{$ClassID}" -eq "{D20EA4E1-3957-11D2-A40B-0C5020524153}") {
$AppType = "Control Panel" # Windows 7 Administrative Tools
} elseif ($Self.Name -eq $Self.Path) { # TODO: Improve this test, which is very weak
$AppType = "Search Results" # Ex: "Search Results in Indexed Locations"
} else {
$AppType = "Unknown"
}
The full algorithm, with the proper precautions to eliminate undefined fields, or invalid values, etc, is implemented in this script:
https://github.com/JFLarvoire/SysToolsLib/blob/master/PowerShell/ShellApp.ps1
One solution is to test whether the Shell Folder (IShellFolder) beneath the Shell View that Windows sends back is handled by the Windows file system or by some custom folder.
For that, you can use the System.NamespaceCLSID Windows property. If the folder associated with the view is handled by the file system, this property value will be the ShellFSFolder GUID value which equal to f3364ba0-65b9-11ce-a9ba-00aa004ae837 (from Windows SDK shobjidl_core.h).
You can test it with something like this in PowerShell:
$ShellFSFolder = [System.Guid]::New("f3364ba0-65b9-11ce-a9ba-00aa004ae837")
foreach($win in (New-Object -com "Shell.Application").Windows()) {
$clsid = $win.Document.Folder.Self.ExtendedProperty("System.NamespaceCLSID")
if ($clsid -ne $null) {
$clsid = [System.Guid]::New($clsid)
if ($clsid -eq $ShellFSFolder) {
Write-Host $win.Document.Folder.Self.Path
}
}
}
And like this in C#:
var ShellFSFolder = new Guid("f3364ba0-65b9-11ce-a9ba-00aa004ae837");
dynamic shell = Activator.CreateInstance(Type.GetTypeFromProgID("Shell.Application"));
foreach (var win in shell.Windows)
{
var clsid = win.Document.Folder.Self.ExtendedProperty("System.NamespaceCLSID");
if (clsid != null)
{
Guid guid;
if (clsid is byte[] bytes)
{
guid = new Guid(bytes);
}
else
{
guid = new Guid((string)clsid);
}
if (guid == ShellFSFolder)
{
Console.WriteLine(win.Document.Folder.Title); // for example
}
}
}
It seems that only file-path-based File Explorer windows have a non-$null .LocationUrl property value, so you can filter by that:
Caveat: Jean-François reports that this approach doesn't work for Explorer windows that are open to a file-system folder located on a connected smartphone, in which case .LocationUrl is apparently $null too.
$explorerWinsWithFilePaths =
(New-Object -com "Shell.Application").Windows() | Where-Object LocationUrl
To extract the file paths that these windows are displaying (the technique also works with non-file locations such as Quick Access, which translate into ::-prefixed GUIDs):
$explorerWinsWithFilePaths.Document.Folder.Self.Path
See Jean-François' comment below for examples of what windows showing folders on a connected smartphone report.

Windows 10 UWP App Launch from Package List (FindPackage)

My goal is to launch a file from an app that the user specifies in my UWP app. When I hit on the Launch button, I navigate backwards into my AppData (UWP) Packages folder, then I iterate through folder and utilize the PackageManager class to retrieve packages by name. I try to initialize a package with the right package name using FindPackage (here). However, it doesn't work for me and I get an exception "Value does not fall within the expected range" when calling FindPackage.
Here's what I've tried so far:
StorageFolder currApp = Windows.Storage.ApplicationData.Current.LocalFolder;
Debug.WriteLine(currApp.Path);
// move up and get parent folder for all packages
DirectoryInfo currAppFolder = Directory.GetParent(currApp.Path);
DirectoryInfo pkgs = Directory.GetParent(currAppFolder.FullName);
StorageFolder pkgFldr = await StorageFolder.GetFolderFromPathAsync(pkgs.FullName);
var pkgDirs = await pkgFldr.GetFoldersAsync();
Package UwpPkg = null;
var PkgMgr = new PackageManager();
foreach (StorageFolder dir in pkgDirs)
{
string folderName = dir.Name;
var currPkg = PkgMgr.FindPackage(folderName);
if (filter == currPkg.DisplayName)
{
// we found it
UwpPkg = currPkg;
break;
}
}
I've also already configured necessary permissions (rescap: broadFileSystemAccess).
Did someone also have such problems/a possible solution? Thank you!
Windows 10 UWP App Launch from Package List (FindPackage)
Please check PackageManager document, for this api it need packageQuery capability
<Package
xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"
IgnorableNamespaces="uap mp rescap" >
......
<rescap:Capability Name="packageQuery" />
And please note
This method requires administrative privileges. The returned package may be installed for the current user or for another user. So please invoke above in the elevated desktop extension. For more you could stefan's blog App Elevation Samples.

Windows shortcuts not appearing after installation

I'm writing an installer for my application and some start menu shortcuts are created during the installation. However, unless I install as administrator (which isn't required) some of the shortcuts fail to appear in the start menu after installation despite all of them being written. Here's the code I've written for reference:
IWshRuntimeLibrary.WshShell shell = new IWshRuntimeLibrary.WshShell();
IWshRuntimeLibrary.IWshShortcut help_shortcut = shell.CreateShortcut(shortcut_path + "\\KShootMania Skin Manager\\Help.lnk");
help_shortcut.TargetPath = startup_shortcut.TargetPath;
help_shortcut.Arguments = "help";
help_shortcut.Save();
IWshRuntimeLibrary.IWshShortcut uninstall_shortcut = shell.CreateShortcut(shortcut_path + "\\KShootMania Skin Manager\\Uninstall.lnk");
uninstall_shortcut.TargetPath = startup_shortcut.TargetPath;
uninstall_shortcut.Arguments = "uninstall";
uninstall_shortcut.Save();

clear icon cache in win 7 programmatically - execute ie4uinit.exe-ClearIconCache using C# or Visual Basic

We changed the logo-icon of our WPF application, and then the icon of the main executable. On my PC with Win 7, there is a problem with the refresh of the icon cache: the desktop shortcut to the main executable, and the preview of the icon of the executable, in Windows Explorer still shows the old icon.
Even restarting the system the problem persists.
I found that running this command solves the problem:
ie4uinit.exe-ClearIconCache
My problem is that I can't run it from code. I made two attempts.
First:
Dim si As New ProcessStartInfo()
si.CreateNoWindow = False
si.UseShellExecute = False
si.FileName = "ie4uinit.exe"
si.WindowStyle = ProcessWindowStyle.Hidden
si.Arguments = "-ClearIconCache"
Dim p As Process = Process.Start(si)
error: "Could not find the specified file" - I tried to input the full path but it still doesn't find the file
Second:
I put the command in a batch file
Dim si As New ProcessStartInfo("C:\test.bat")
si.UseShellExecute = False
si.RedirectStandardError = True
si.RedirectStandardInput = True
si.RedirectStandardOutput = True
si.CreateNoWindow = True
si.ErrorDialog = False
si.WindowStyle = ProcessWindowStyle.Hidden
Dim p As Process = Process.Start(si)
This time I get no errors, but not even the desired effect. If I double-click on the batch file instead, everything is working fine.
I'd like to adjust one of these procedure otherwise finding a new one to clear the windows icon cache. C# or Visual Basic is not important...
Pileggi
maybe it doesn't search for it in the path try using:
as the path "%WINDIR%\System32\ie4uinit.exe",
if this doesnt work try "C:\Windows\System32\ie4uinit.exe"
I found the solution: I had to build the executable that runs the batch file for "Any CPU", otherwise it has not sufficient permissions to run ie4unit.
Before I was trying building for "x86" and I was running the process on a Win7 64 bit...
I had a similar issue, trying to call ie4uinit from an Inno installer. The PATH did include the right system directories; however, doing a "dir" did not show that the file exists. In fact, there were over 100 *.exe files that could not be found from whatever shell was executing the command. Opening Explorer or a command window reveals the file is there (which of course we know). I think it is a permissions or access issue. I didn't have the patience to trace it further, but just copied ie4uinit.exe to a local directory and had my installer execute it there.
Enables or disables file system redirection for the calling thread.
[DllImport("Kernel32.dll")]
private static extern bool Wow64EnableWow64FsRedirection(bool Wow64FsEnableRedirection);
//.....
Wow64EnableWow64FsRedirection(false);
Dim p As Process = Process.Start(si)
Wow64EnableWow64FsRedirection(true);
You can try this:
Dim objProcess As System.Diagnostics.Process
objProcess = New System.Diagnostics.Process()
objProcess.StartInfo.FileName = "ie4uinit.exe"
objProcess.StartInfo.Arguments = "-ClearIconCache"
objProcess.StartInfo.WindowStyle = ProcessWindowStyle.Normal
objProcess.Start()
objProcess.WaitForExit()
objProcess.Close()

Context Shell Menus - Get identifier on click

I have created a context shell menu for / on .txt files.
Its 'action' is similar to that of 'Edit with notepad' option.
I am able to open 'notepad' on clicking the menu using this code -
subKey.SetValue("", "C:\\Windows\\System32\\notepad.exe");
//subKey is the newly created sub key - The key creation part works fine.
How will I be able to use a feature similar to that of the 'Edit with notepad' feature? Or is it at least possible to get the name of the '.txt' file on which this event was triggered?
Note: By 'Edit with notepad', I mean viewing the selected file's contents in notepad.
The shell (explorer.exe) will substitute %1 with the file name. So you basically write:
Windows Registry Editor Version 5.00
[HKEY_CLASSES_ROOT\txtfile\shell\openwithnotepad]
#="Open with &Notepad"
[HKEY_CLASSES_ROOT\txtfile\shell\openwithnotepad\command]
#="\"C:\\Windows\\System32\\notepad.exe\" \"%1\""
The file name will be passed to C:\Windows\System32\notepad.exe as a command line argument. For example if you open D:\blah.txt, then notepad will receive D:\blah.txt as the first argument.
In C#, you basically use either Environment.GetCommandLineArgs() or args in Main to retrieve the file path.
An example:
string[] commandLineArgs = Environment.GetCommandLineArgs();
string fileToOpen = null;
if (commandLineArgs.Length > 1)
{
if (File.Exists(commandLineArgs[1]))
{
fileToOpen = commandLineArgs[1];
}
}
if (fileToOpen == null)
{
// new file
}
else
{
MyEditor.OpenFile(fileToOpen);
}

Categories

Resources