How to follow a .lnk file programmatically - c#

We have a network drive full of shortcuts (.lnk files) that point to folders and I need to traverse them programmatically in a C# Winforms app.
What practical options do I have?

Add IWshRuntimeLibrary as a reference to your project. Add Reference, COM tab, Windows Scripting Host Object Model.
Here is how I get the properties of a shortcut:
IWshRuntimeLibrary.IWshShell wsh = new IWshRuntimeLibrary.WshShellClass();
IWshRuntimeLibrary.IWshShortcut sc = (IWshRuntimeLibrary.IWshShortcut)wsh.CreateShortcut(filename);
The shortcut object "sc" has a TargetPath property.

If you do not wish to reference COM, and distribute the Interop.IWshRuntimeLibrary.dll with your product (remembering Jay Riggs "Embed Interop Types": False)
You can use the new dynamic COM instead.
private void Window_Drop(object sender, DragEventArgs e)
{
if (e.Data.GetDataPresent(DataFormats.FileDrop))
{
string[] files = (string[])e.Data.GetData(DataFormats.FileDrop);
dynamic shortcut;
dynamic windowsShell;
try
{
var file = files[0];
if (Path.GetExtension(file)?.Equals(".lnk",StringComparison.OrdinalIgnoreCase) == true)
{
Type shellObjectType = Type.GetTypeFromProgID("WScript.Shell");
windowsShell = Activator.CreateInstance(shellObjectType);
shortcut = windowsShell.CreateShortcut(file);
file = shortcut.TargetPath;
// Release the COM objects
shortcut = null;
windowsShell = null;
}
//
// <use file>...
//
}
finally
{
// Release the COM objects
shortcut = null;
windowsShell = null;
}
}
}

I know it is not the correct way and that lnk file structures can change etc., but this is what I do:
private static string LnkToFile(string fileLink)
{
string link = File.ReadAllText(fileLink);
int i1 = link.IndexOf("DATA\0");
if (i1 < 0)
return null;
i1 += 5;
int i2 = link.IndexOf("\0", i1);
if (i2 < 0)
return link.Substring(i1);
else
return link.Substring(i1, i2 - i1);
}

Load the file using the COM IPersistFile interface.
Do a QueryInterface on the result to turn it into an IShellLink interface.
Call IShellLink::GetPath
As far as I am aware you can have .NET generate classes conforming to each of these interfaces for you using the "Add Reference" dialog box.

The IShellLink interface lets you manipulate .lnk files, though it's a bit of a pain to use from C#.
This article has some code implementing the necessary interop gubbins.
Update
You can find the code from the article here but the page doesn't seem to work in Firefox. It does work in IE.

Related

MsiGetShortcutTarget MSI LNK files return no component id

I am trying to programmatically find the exe file that is run for a given MSI lnk file (advertised shortcut). I have used an approach similar to that shown in the answer to Is there a way to resolve a .lnk target that works for links that end up in c:\windows\installer?. This approach works fine for the majority of MSI lnk files. Unfortunately there are a minority of lnk files that run fine, but MsiGetShortcutTarget returns no component id. So the subsequent call to MsiGetComponentPath returns InvalidArg.
Here's the code I'm using (taken from here):
public const int MaxFeatureLength = 38;
public const int MaxGuidLength = 38;
public const int MaxPathLength = 1024;
public static string ParseShortcut(string shortcutFilename)
{
StringBuilder product = new StringBuilder(MaxGuidLength + 1);
StringBuilder feature = new StringBuilder(MaxFeatureLength + 1);
StringBuilder component = new StringBuilder(MaxGuidLength + 1);
var returnValue = MsiGetShortcutTarget(shortcutFilename, product, feature, component);
if (returnValue != 0)
{
return null;
}
int pathLength = MaxPathLength;
StringBuilder path = new StringBuilder(pathLength);
InstallState installState = MsiGetComponentPath(product.ToString(), component.ToString(), path, ref pathLength);
if (installState == InstallState.Local)
{
return path.ToString();
}
else
{
return null;
}
}
An example on my machine is C:\ProgramData\Microsoft\Windows\Start Menu\Programs\Microsoft Office 2013\Office 2013 Tools\Office 2013 Language Preferences.lnk
Product id: {91150000-0011-0000-0000-0000000FF1CE}
Feature id: SetLanguageFiles
I believe the IShellLink interface cannot be used to return the runnable exe for MSI Advertised Shortcuts and my attempts to do so have returned the path to an exe containing icon resources.
Clearly it is possible for the operating system to locate the appropriate exe (in this case it is C:\Program Files (x86)\Microsoft Office\Office15\SETLANG.EXE).
How do I get which exe file is associated with this lnk file using code?
When no component id is returned from MsiGetShortcutTarget, it is possible to get the component id from the MSI database. One way of doing this is to use WiX DTF as shown here: https://stackoverflow.com/a/34680682/3051702.
The returned component id can then be used in a call to MsiGetComponentPath. Alternatively, the native methods could be used directly.

How to close a file in Autocad using C# keeping acad.exe running?

I am using visual studio 2010 and I am having a .DWG file which I want to open in autocad. Till now I have used this.
Process p = new Process();
ProcessStartInfo s = new ProcessStartInfo("D:/Test File/" + fileName);
p.StartInfo = s;
p.Start();
But what I want is to close the file inside the Autocad but not the autocad itself. (Means atocad.exe should be kept running).
Till now I hve used this but its closing the acad.exe not the file.
foreach (Process Proc in Process.GetProcesses())
{
if (Proc.ProcessName.Equals("acad"))
{
Proc.CloseMainWindow();
Proc.Kill();
}
}
Take the Autocad .NET libraries from Autodesk Sites (http://usa.autodesk.com/adsk/servlet/index?id=773204&siteID=123112)
Then you will be able to use Application and Document classes.
They will give you full control over opening and closing documents within the application.
You can find many articles on that, and can ask further questions.
AutoCAD does have an api. there are 4 assemblys. Two for in-process and two for COM.
inprocess :
acdbmgd.dll
acmgd.dll
COMInterop :
Autodesk.Autocad.Interop.dll
Autodesk.Autocad.Interop.Common.dll
this is a method that will open a new instance of AutoCAD or it will connect to an existing running instance of AutoCAD.
you will need to load these .dlls into your project references.
using Autodesk.AutoCAD.Interop;
using Autodesk.AutoCAD.Interop.Common;
namespace YourNameSpace {
public class YourClass {
AcadApplication AcApp;
private const string progID = "AutoCAD.Application.18.2";// this is AutoCAD 2012 program id
private string profileName = "<<Unnamed Profile>>";
private const string acadPath = #"C:\Program Files\Autodesk\AutoCAD 2012 - English\acad.exe";
public void GetAcApp()
{
try
{
AcApp = (AcadApplication)Marshal.GetActiveObject(progID);
} catch {
try {
var acadProcess = new Process();
acadProcess.StartInfo.Arguments = string.Format("/nologo /p \"{0}\"", profileName);
acadProcess.StartInfo.FileName = (#acadPath);
acadProcess.Start();
while(AcApp == null)
{
try { AcApp = (AcadApplication)Marshal.GetActiveObject(progID); }
catch { }
}
} catch(COMException) {
MessageBox.Show(String.Format("Cannot create object of type \"{0}\"",progID));
}
}
try {
int i = 0;
var appState = AcApp.GetAcadState();
while (!appState.IsQuiescent)
{
if(i == 120)
{
Application.Exit();
}
// Wait .25s
Thread.Sleep(250);
i++;
}
if(AcApp != null){
// set visibility
AcApp.Visible = true;
}
} catch (COMException err) {
if(err.ErrorCode.ToString() == "-2147417846"){
Thread.Sleep(5000);
}
}
}
}
}
closeing it is as simple as
Application.Exit();
and forgive the code. its atrocious, this was one of my first methods when i just started developing...
I doubt you will be able to do this unless AutoCAD has an API that you can hook into and ask it to close the file for you.
Your c# app can only do things to the process (acad.exe) , it doesn't have access to the internal operations of that process.
Also, you shouldn't use Kill unless the process has become unresponsive and certainly not immediately after CloseMainWindow.
CloseMainWindow is the polite way to ask an application to close itself. Kill is like pulling the power lead from the socket. You aren't giving it the chance to clean up after itself and exit cleanly.
There is one other possibility - this will only work if your C# code is running on the same machine as the AutoCAD process and it is not really recommended, but, if you are really stuck and are prepared to put up with the hassle of window switching you can send key strokes to an application using the SendKeys command.
MSDN articles here:
http://msdn.microsoft.com/EN-US/library/ms171548(v=VS.110,d=hv.2).aspx
http://msdn.microsoft.com/en-us/library/system.windows.forms.sendkeys.send.aspx
Using this you could send the key strokes to simulate the user using the menu commands to close the file.
To perform the closing of file, best way out is to follow the steps at this ObjectARX SDK for c# and change the following code with the below code.
[CommandMethod("CD", CommandFlags.Session)]
static public void CloseDocuments()
{
DocumentCollection docs = Application.DocumentManager;
foreach (Document doc in docs)
{
// First cancel any running command
if (doc.CommandInProgress != "" &&
doc.CommandInProgress != "CD")
{
AcadDocument oDoc =
(AcadDocument)doc.AcadDocument;
oDoc.SendCommand("\x03\x03");
}
if (doc.IsReadOnly)
{
doc.CloseAndDiscard();
}
else
{
// Activate the document, so we can check DBMOD
if (docs.MdiActiveDocument != doc)
{
docs.MdiActiveDocument = doc;
}
int isModified =
System.Convert.ToInt32(
Application.GetSystemVariable("DBMOD")
);
// No need to save if not modified
if (isModified == 0)
{
doc.CloseAndDiscard();
}
else
{
// This may create documents in strange places
doc.CloseAndSave(doc.Name);
}
}
}

Write file extended property "Revision Number" on all type of files

I would like to read/write the window information file (extended file properties) using c#
The one found by doing the following: In window explorer right click => properties => Summary tab. I want mainly to have access to the properties:
Title
Category
Revision Number
For office document I can use the following (using Office.Interop) or using DSOFile
private static string GetExcelWorkbookPropertyValue(_Workbook workbook, string propertyName)
{
DocumentProperties builtInProperties = (DocumentProperties)workbook.BuiltinDocumentProperties;
string value = builtInProperties.Cast<DocumentProperty>().First(x => x.Name.Equals(propertyName, StringComparison.InvariantCultureIgnoreCase)).Value;
return value ?? "";
}
But what i would like is a more general solution that will work with all files.
Could someone help?
Question additional information you can also read the properties Title and Category by using Shell32
Shell32.Shell shell = new Shell32.Shell();
//set the namespace to file path
Shell32.Folder folder = shell.NameSpace(Path.GetDirectoryName(file));
//get ahandle to the file
Shell32.FolderItem folderItem = folder.ParseName(Path.GetFileName(file));
//did we get a handle ?
if (folderItem != null)
{
for (int i = 0; i < 100; i++)
{
string s = folder.GetDetailsOf(folderItem, i);
System.Diagnostics.Debug.WriteLine(s);
}
}
However I still write the properity Revision Number, however it's look like Revision number is an office document property and cannot be written (I guess it will break the tracking process of office).
What does not make sense is that I can modify it using window explorer and the property is also visible for non-office documents... I'm struggling to understand that.
This information is stored in properties. Here are some of the standard properties. I'm not sure if the .NET Framework provides a wrapper around these interfaces, though.

C# Visual Studio 2008 Reference to system32.dll ... how?

I need the reference system32/shell32.dll as I use some shell functions to read out the recycling bin. I tried "Add Reference --> COM --> Microsoft Shell Controls and Automatation" and "Add Reference --> Browse ---> [going to the system32/shell32.dll directly]. Both adds the shell32 reference to my references. But when I look at the properties, I see the path of the reference looks like this: "C:\Users\Tim\Documents\Visual Studio 2008\Projects\Wing\FileWing\obj\Debug\Interop.Shell32.dll" ...
I'll not deploy this \obj\Debug\ path to my installer. So how can I reference the end-users shell32.dll directly? Is there a way? Why does VS2008 create this strange path? Can I change this path so it doesn't sit in this strange subfolder?
Hmmm. Okay after revisiting PInvoke, I'm sure that I don't quite get it :-/
Let me illustrate the code I need to handle. I'm digging though the recycling bin and seek for a item that I want to recover. Is there any way NOT fighting though the PInvoke to get this done?
private void recoverRecyclerBinEntry(string fileName, int size)
{
try
{
Shell Shl = new Shell();
Folder Recycler = Shl.NameSpace(10);
// scans through all the recyclers entries till the one to recover has been found
for (int i = 0; i < Recycler.Items().Count; i++)
{
FolderItem FI = Recycler.Items().Item(i);
string FileName = Recycler.GetDetailsOf(FI, 0);
if (Path.GetExtension(FileName) == "")
FileName += Path.GetExtension(FI.Path);
//Necessary for systems with hidden file extensions.
string FilePath = Recycler.GetDetailsOf(FI, 1);
string combinedPath = Path.Combine(FilePath, FileName);
if (size == FI.Size && fileName == combinedPath)
{
Debug.Write("Match found. Restoring " + combinedPath + "...");
Undelete(FI);
Debug.WriteLine("done.");
}
else
{
Debug.WriteLine("No match");
}
}
}
catch (Exception ex)
{
Debug.WriteLine(ex.Message);
Debug.WriteLine(ex.StackTrace);
}
}
private bool Undelete(FolderItem Item)
{
try
{
foreach (FolderItemVerb FIVerb in Item.Verbs())
{
if (
(FIVerb.Name.ToUpper().Contains("WIEDERHERSTELLEN")) ||
(FIVerb.Name.ToUpper().Contains("ESTORE")) ||
(FIVerb.Name.ToUpper().Contains("NDELETE"))
)
{
FIVerb.DoIt();
return true;
}
}
//execute the first one:
Item.Verbs().Item(0).DoIt();
return true;
}
catch (Exception)
{
Debug.WriteLine("ERROR undeleting");
return false;
}
}
I believe you are looking for P/Invoke (Platform Invoke)
Once you get the method for including and using the DLLs down, you can visit pinvoke.net to get specific code snippets for using certain methods.
Are you just using DllImport to access functionality in shell32/kernel32? If so, you don't need to add a reference.
For example:
[DllImport("KERNEL32.DLL", EntryPoint="MoveFileW", SetLastError=true,
CharSet=CharSet.Unicode, ExactSpelling=true,
CallingConvention=CallingConvention.StdCall)]
public static extern bool MoveFile(String src, String dst);
Here's a tutorial on using platform invoke and here's an MSDN article.
After you add the dll reference using VS 2008, you can open the properties for the .dll.
Make sure Copy Local is set to True.
If that doesn't work another solution is to add the .dll as an item to you project, and make is as content, and tell it to copy to the output directory.

Creating application shortcut in a directory

How do you create an application shortcut (.lnk file) in C# or using the .NET framework?
The result would be a .lnk file to the specified application or URL.
It's not as simple as I'd have liked, but there is a great class call ShellLink.cs at
vbAccelerator
This code uses interop, but does not rely on WSH.
Using this class, the code to create the shortcut is:
private static void configStep_addShortcutToStartupGroup()
{
using (ShellLink shortcut = new ShellLink())
{
shortcut.Target = Application.ExecutablePath;
shortcut.WorkingDirectory = Path.GetDirectoryName(Application.ExecutablePath);
shortcut.Description = "My Shorcut Name Here";
shortcut.DisplayMode = ShellLink.LinkDisplayMode.edmNormal;
shortcut.Save(STARTUP_SHORTCUT_FILEPATH);
}
}
Nice and clean. (.NET 4.0)
Type t = Type.GetTypeFromCLSID(new Guid("72C24DD5-D70A-438B-8A42-98424B88AFB8")); //Windows Script Host Shell Object
dynamic shell = Activator.CreateInstance(t);
try{
var lnk = shell.CreateShortcut("sc.lnk");
try{
lnk.TargetPath = #"C:\something";
lnk.IconLocation = "shell32.dll, 1";
lnk.Save();
}finally{
Marshal.FinalReleaseComObject(lnk);
}
}finally{
Marshal.FinalReleaseComObject(shell);
}
That's it, no additional code needed. CreateShortcut can even load shortcut from file, so properties like TargetPath return existing information. Shortcut object properties.
Also possible this way for versions of .NET unsupporting dynamic types. (.NET 3.5)
Type t = Type.GetTypeFromCLSID(new Guid("72C24DD5-D70A-438B-8A42-98424B88AFB8")); //Windows Script Host Shell Object
object shell = Activator.CreateInstance(t);
try{
object lnk = t.InvokeMember("CreateShortcut", BindingFlags.InvokeMethod, null, shell, new object[]{"sc.lnk"});
try{
t.InvokeMember("TargetPath", BindingFlags.SetProperty, null, lnk, new object[]{#"C:\whatever"});
t.InvokeMember("IconLocation", BindingFlags.SetProperty, null, lnk, new object[]{"shell32.dll, 5"});
t.InvokeMember("Save", BindingFlags.InvokeMethod, null, lnk, null);
}finally{
Marshal.FinalReleaseComObject(lnk);
}
}finally{
Marshal.FinalReleaseComObject(shell);
}
I found something like this:
private void appShortcutToDesktop(string linkName)
{
string deskDir = Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory);
using (StreamWriter writer = new StreamWriter(deskDir + "\\" + linkName + ".url"))
{
string app = System.Reflection.Assembly.GetExecutingAssembly().Location;
writer.WriteLine("[InternetShortcut]");
writer.WriteLine("URL=file:///" + app);
writer.WriteLine("IconIndex=0");
string icon = app.Replace('\\', '/');
writer.WriteLine("IconFile=" + icon);
writer.Flush();
}
}
Original code at sorrowman's article "url-link-to-desktop"
After surveying all possibilities I found on SO I've settled on ShellLink:
//Create new shortcut
using (var shellShortcut = new ShellShortcut(newShortcutPath)
{
Path = path
WorkingDirectory = workingDir,
Arguments = args,
IconPath = iconPath,
IconIndex = iconIndex,
Description = description,
})
{
shellShortcut.Save();
}
//Read existing shortcut
using (var shellShortcut = new ShellShortcut(existingShortcut))
{
path = shellShortcut.Path;
args = shellShortcut.Arguments;
workingDir = shellShortcut.WorkingDirectory;
...
}
Apart of being simple and effective, the author (Mattias Sjögren, MS MVP) is some sort of COM/PInvoke/Interop guru, and perusing his code I believe it is more robust than the alternatives.
It should be mentioned that shortcut files can also be created by several commandline utilities (which in turn can be easily invoked from C#/.NET). I never tried any of them, but I'd start with NirCmd (NirSoft have SysInternals-like quality tools).
Unfortunately NirCmd can't parse shortcut files (only create them), but for that purpose TZWorks lp seems capable. It can even format its output as csv. lnk-parser looks good too (it can output both HTML and CSV).
Donwload IWshRuntimeLibrary
You also need to import of COM library IWshRuntimeLibrary. Right click on your project -> add reference -> COM -> IWshRuntimeLibrary -> add and then use the following code snippet.
private void createShortcutOnDesktop(String executablePath)
{
// Create a new instance of WshShellClass
WshShell lib = new WshShellClass();
// Create the shortcut
IWshRuntimeLibrary.IWshShortcut MyShortcut;
// Choose the path for the shortcut
string deskDir = Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory);
MyShortcut = (IWshRuntimeLibrary.IWshShortcut)lib.CreateShortcut(#deskDir+"\\AZ.lnk");
// Where the shortcut should point to
//MyShortcut.TargetPath = Application.ExecutablePath;
MyShortcut.TargetPath = #executablePath;
// Description for the shortcut
MyShortcut.Description = "Launch AZ Client";
StreamWriter writer = new StreamWriter(#"D:\AZ\logo.ico");
Properties.Resources.system.Save(writer.BaseStream);
writer.Flush();
writer.Close();
// Location for the shortcut's icon
MyShortcut.IconLocation = #"D:\AZ\logo.ico";
// Create the shortcut at the given path
MyShortcut.Save();
}
Similar to IllidanS4's answer, using the Windows Script Host proved the be the easiest solution for me (tested on Windows 8 64 bit).
However, rather than importing the COM type manually through code, it is easier to just add the COM type library as a reference. Choose References->Add Reference..., COM->Type Libraries and find and add "Windows Script Host Object Model".
This imports the namespace IWshRuntimeLibrary, from which you can access:
WshShell shell = new WshShell();
IWshShortcut link = (IWshShortcut)shell.CreateShortcut(LinkPathName);
link.TargetPath=TargetPathName;
link.Save();
Credit goes to Jim Hollenhorst.

Categories

Resources