I'm referring this thread to refresh the windows explorer, I want to refresh some windows only, which means I want to filter the opened windows according to their title or path. Let me copy the code from that thread for more clarification:
Guid CLSID_ShellApplication = new Guid("13709620-C279-11CE-A49E-444553540000");
Type shellApplicationType = Type.GetTypeFromCLSID(CLSID_ShellApplication, true);
object shellApplication = Activator.CreateInstance(shellApplicationType);
object windows = shellApplicationType.InvokeMember("Windows", System.Reflection.BindingFlags.InvokeMethod, null, shellApplication, new object[] { });
Type windowsType = windows.GetType();
object count = windowsType.InvokeMember("Count", System.Reflection.BindingFlags.GetProperty, null, windows, null);
for (int i = 0; i < (int)count; i++)
{
object item = windowsType.InvokeMember("Item", System.Reflection.BindingFlags.InvokeMethod, null, windows, new object[] { i });
Type itemType = item.GetType();
string itemName = (string)itemType.InvokeMember("Name", System.Reflection.BindingFlags.GetProperty, null, item, null);
if (itemName == "Windows Explorer")
{
// Here I want to check whether this window need to be refreshed
// based on the opened path in that window
// or with the title of that window
// How do I check that here
itemType.InvokeMember("Refresh", System.Reflection.BindingFlags.InvokeMethod, null, item, null);
}
}
What I understood from the above code is: By using this line windowsType.InvokeMember("Item", System.Reflection.BindingFlags.InvokeMethod, null, windows, new object[] { i }); we will get the current window object, and then we are using .InvokeMember("Name".. to get the name of that object, like wise what should I pass to InvokeMember method to get the path of that object or the title of that window? or can anyone tell me the possible alternative values for "Name" in the above statement?
What I'm expecting is some code like the following:
string itemPath = (string)itemType.InvokeMember("Something here", System.Reflection.BindingFlags.GetProperty, null, item, null);
OR
string itemTitle = (string)itemType.InvokeMember("Something here", System.Reflection.BindingFlags.GetProperty, null, item, null);
I can give you more information if you need, expecting expert's suggestion to solve this issue,
Thanks in advance
This is the way you had to write late-bound COM client code in the Bad Old Days. Considerable pain and suffering to get it going, what is in the snippet is not close yet. I'll first propose a very different way to do this, there just isn't any point in doing it late-bound since these COM objects are available on any Windows version and are never going to change anymore. The "Embed Interop Types" feature supported since VS2010 removes any good reason to avoid it.
Project > Add Reference > COM tab. Tick "Microsoft Internet Controls" and "Microsoft Shell Controls and Automation". Now you can write it early-bound, nice and compact with all the benefits of IntelliSense to help you find the correct members and avoid typos:
var shl = new Shell32.Shell();
foreach (SHDocVw.InternetExplorer win in shl.Windows()) {
var path = win.LocationURL;
if (!path.StartsWith("file:///")) continue;
path = System.IO.Path.GetFullPath(path.Substring(8));
if (path.StartsWith("C")) win.Refresh();
}
A slightly silly example, it refreshes any Explorer window who's displayed path is located on the C drive. Note how the Path property is not useful to discover what is displayed, LocationURL is needed. You might have to find the distinction between Internet Explorer and Windows Explorer windows (aka "File Explorer"), albeit that IE can also display directory content so I think this is the most correct version.
If you really want to do this late-bound then use the dynamic keyword to minimize the suffering. In this case almost identical:
dynamic shl = Activator.CreateInstance(Type.GetTypeFromProgID("Shell.Application"));
foreach (var win in shl.Windows()) {
string path = win.LocationURL;
if (!path.StartsWith("file:///")) continue;
path = System.IO.Path.GetFullPath(path.Substring(8));
if (path.StartsWith("C")) win.Refresh();
}
Answering your question explicitly, use "LocationURL".
Related
I'm trying to find out how to read/write to the extended file properties in C#
e.g. Comment, Bit Rate, Date Accessed, Category etc that you can see in Windows explorer.
Any ideas how to do this?
EDIT: I'll mainly be reading/writing to video files (AVI/DIVX/...)
For those of not crazy about VB, here it is in c#:
Note, you have to add a reference to Microsoft Shell Controls and Automation from the COM tab of the References dialog.
public static void Main(string[] args)
{
List<string> arrHeaders = new List<string>();
Shell32.Shell shell = new Shell32.Shell();
Shell32.Folder objFolder;
objFolder = shell.NameSpace(#"C:\temp\testprop");
for( int i = 0; i < short.MaxValue; i++ )
{
string header = objFolder.GetDetailsOf(null, i);
if (String.IsNullOrEmpty(header))
break;
arrHeaders.Add(header);
}
foreach(Shell32.FolderItem2 item in objFolder.Items())
{
for (int i = 0; i < arrHeaders.Count; i++)
{
Console.WriteLine(
$"{i}\t{arrHeaders[i]}: {objFolder.GetDetailsOf(item, i)}");
}
}
}
Solution 2016
Add following NuGet packages to your project:
Microsoft.WindowsAPICodePack-Shell by Microsoft
Microsoft.WindowsAPICodePack-Core by Microsoft
Read and Write Properties
using Microsoft.WindowsAPICodePack.Shell;
using Microsoft.WindowsAPICodePack.Shell.PropertySystem;
string filePath = #"C:\temp\example.docx";
var file = ShellFile.FromFilePath(filePath);
// Read and Write:
string[] oldAuthors = file.Properties.System.Author.Value;
string oldTitle = file.Properties.System.Title.Value;
file.Properties.System.Author.Value = new string[] { "Author #1", "Author #2" };
file.Properties.System.Title.Value = "Example Title";
// Alternate way to Write:
ShellPropertyWriter propertyWriter = file.Properties.GetPropertyWriter();
propertyWriter.WriteProperty(SystemProperties.System.Author, new string[] { "Author" });
propertyWriter.Close();
Important:
The file must be a valid one, created by the specific assigned software. Every file type has specific extended file properties and not all of them are writable.
If you right-click a file on desktop and cannot edit a property, you wont be able to edit it in code too.
Example:
Create txt file on desktop, rename its extension to docx. You can't
edit its Author or Title property.
Open it with Word, edit and save
it. Now you can.
So just make sure to use some try catch
Further Topic:
Microsoft Docs: Implementing Property Handlers
There's a CodeProject article for an ID3 reader. And a thread at kixtart.org that has more information for other properties. Basically, you need to call the GetDetailsOf() method on the folder shell object for shell32.dll.
This sample in VB.NET reads all extended properties:
Sub Main()
Dim arrHeaders(35)
Dim shell As New Shell32.Shell
Dim objFolder As Shell32.Folder
objFolder = shell.NameSpace("C:\tmp")
For i = 0 To 34
arrHeaders(i) = objFolder.GetDetailsOf(objFolder.Items, i)
Next
For Each strFileName In objfolder.Items
For i = 0 To 34
Console.WriteLine(i & vbTab & arrHeaders(i) & ": " & objfolder.GetDetailsOf(strFileName, i))
Next
Next
End Sub
You have to add a reference to Microsoft Shell Controls and Automation from the COM tab of the References dialog.
Thank you guys for this thread! It helped me when I wanted to figure out an exe's file version. However, I needed to figure out the last bit myself of what is called Extended Properties.
If you open properties of an exe (or dll) file in Windows Explorer, you get a Version tab, and a view of Extended Properties of that file. I wanted to access one of those values.
The solution to this is the property indexer FolderItem.ExtendedProperty and if you drop all spaces in the property's name, you'll get the value. E.g. File Version goes FileVersion, and there you have it.
Hope this helps anyone else, just thought I'd add this info to this thread. Cheers!
GetDetailsOf() Method - Retrieves details about an item in a folder. For example, its size, type, or the time of its last modification. File Properties may vary based on the Windows-OS version.
List<string> arrHeaders = new List<string>();
Shell shell = new ShellClass();
Folder rFolder = shell.NameSpace(_rootPath);
FolderItem rFiles = rFolder.ParseName(filename);
for (int i = 0; i < short.MaxValue; i++)
{
string value = rFolder.GetDetailsOf(rFiles, i).Trim();
arrHeaders.Add(value);
}
Jerker's answer is little simpler. Here's sample code which works from MS:
var folder = new Shell().NameSpace(folderPath);
foreach (FolderItem2 item in folder.Items())
{
var company = item.ExtendedProperty("Company");
var author = item.ExtendedProperty("Author");
// Etc.
}
For those who can't reference shell32 statically, you can invoke it dynamically like this:
var shellAppType = Type.GetTypeFromProgID("Shell.Application");
dynamic shellApp = Activator.CreateInstance(shellAppType);
var folder = shellApp.NameSpace(folderPath);
foreach (var item in folder.Items())
{
var company = item.ExtendedProperty("Company");
var author = item.ExtendedProperty("Author");
// Etc.
}
After looking at a number of solutions on this thread and elsewhere
the following code was put together. This is only to read a property.
I could not get the
Shell32.FolderItem2.ExtendedProperty function to work, it is supposed
to take a string value and return the correct value and type for that
property... this was always null for me and developer reference resources were very thin.
The WindowsApiCodePack seems
to have been abandoned by Microsoft which brings us the code below.
Use:
string propertyValue = GetExtendedFileProperty("c:\\temp\\FileNameYouWant.ext","PropertyYouWant");
Will return you the value of the extended property you want as a
string for the given file and property name.
Only loops until it found the specified property - not until
all properties are discovered like some sample code
Will work on Windows versions like Windows server 2008 where you will get the error "Unable to cast COM object of type 'System.__ComObject' to interface type 'Shell32.Shell'" if just trying to create the Shell32 Object normally.
public static string GetExtendedFileProperty(string filePath, string propertyName)
{
string value = string.Empty;
string baseFolder = Path.GetDirectoryName(filePath);
string fileName = Path.GetFileName(filePath);
//Method to load and execute the Shell object for Windows server 8 environment otherwise you get "Unable to cast COM object of type 'System.__ComObject' to interface type 'Shell32.Shell'"
Type shellAppType = Type.GetTypeFromProgID("Shell.Application");
Object shell = Activator.CreateInstance(shellAppType);
Shell32.Folder shellFolder = (Shell32.Folder)shellAppType.InvokeMember("NameSpace", System.Reflection.BindingFlags.InvokeMethod, null, shell, new object[] { baseFolder });
//Parsename will find the specific file I'm looking for in the Shell32.Folder object
Shell32.FolderItem folderitem = shellFolder.ParseName(fileName);
if (folderitem != null)
{
for (int i = 0; i < short.MaxValue; i++)
{
//Get the property name for property index i
string property = shellFolder.GetDetailsOf(null, i);
//Will be empty when all possible properties has been looped through, break out of loop
if (String.IsNullOrEmpty(property)) break;
//Skip to next property if this is not the specified property
if (property != propertyName) continue;
//Read value of property
value = shellFolder.GetDetailsOf(folderitem, i);
}
}
//returns string.Empty if no value was found for the specified property
return value;
}
Here is a solution for reading - not writing - the extended properties based on what I found on this page and at help with shell32 objects.
To be clear this is a hack. It looks like this code will still run on Windows 10 but will hit on some empty properties. Previous version of Windows should use:
var i = 0;
while (true)
{
...
if (String.IsNullOrEmpty(header)) break;
...
i++;
On Windows 10 we assume that there are about 320 properties to read and simply skip the empty entries:
private Dictionary<string, string> GetExtendedProperties(string filePath)
{
var directory = Path.GetDirectoryName(filePath);
var shell = new Shell32.Shell();
var shellFolder = shell.NameSpace(directory);
var fileName = Path.GetFileName(filePath);
var folderitem = shellFolder.ParseName(fileName);
var dictionary = new Dictionary<string, string>();
var i = -1;
while (++i < 320)
{
var header = shellFolder.GetDetailsOf(null, i);
if (String.IsNullOrEmpty(header)) continue;
var value = shellFolder.GetDetailsOf(folderitem, i);
if (!dictionary.ContainsKey(header)) dictionary.Add(header, value);
Console.WriteLine(header +": " + value);
}
Marshal.ReleaseComObject(shell);
Marshal.ReleaseComObject(shellFolder);
return dictionary;
}
As mentioned you need to reference the Com assembly Interop.Shell32.
If you get an STA related exception, you will find the solution here:
Exception when using Shell32 to get File extended properties
I have no idea what those properties names would be like on a foreign system and couldn't find information about which localizable constants to use in order to access the dictionary. I also found that not all the properties from the Properties dialog were present in the dictionary returned.
BTW this is terribly slow and - at least on Windows 10 - parsing dates in the string retrieved would be a challenge so using this seems to be a bad idea to start with.
On Windows 10 you should definitely use the Windows.Storage library which contains the SystemPhotoProperties, SystemMusicProperties etc.
https://learn.microsoft.com/en-us/windows/uwp/files/quickstart-getting-file-properties
And finally, I posted a much better solution that uses WindowsAPICodePack there
I'm not sure what types of files you are trying to write the properties for but taglib-sharp is an excellent open source tagging library that wraps up all this functionality nicely. It has a lot of built in support for most of the popular media file types but also allows you to do more advanced tagging with pretty much any file.
EDIT: I've updated the link to taglib sharp. The old link no longer worked.
EDIT: Updated the link once again per kzu's comment.
I'd like to know if it's possible to set/edit a file extended properties (Explorer: Right-click > Properties > Details) using the Windows API Code Pack.
var shellFile = Microsoft.WindowsAPICodePack.Shell.ShellObject.FromParsingName(filePath);
var artistName = shellFile.Properties.GetProperty(SystemProperties.System.Music.DisplayArtist).ValueAsObject.ToString();
var duration = TimeSpan.FromMilliseconds(Convert.ToDouble(shellFile.Properties.GetProperty(SystemProperties.System.Media.Duration).ValueAsObject) * 0.0001);
I use these few lines to get the properties I want, but I don't know how to edit one of them (the artist name for example).
I know I can use taglib-sharp, but I'll use it only if there is no solution without external code.
Thanks you all for taking the time to help me.
I found a way to edit some properties with ShellPropertyWriter but some properties are read-only.
var shellFile = ShellFile.FromParsingName(filePath);
ShellPropertyWriter w = shellFile.Properties.GetPropertyWriter();
try
{
w.WriteProperty(SystemProperties.System.Author, new string[] { "MyTest", "Test" });
w.WriteProperty(SystemProperties.System.Music.Artist, new string[] { "MyTest", "Test" });
w.WriteProperty(SystemProperties.System.Music.DisplayArtist, "Test");
}
catch (Exception ex)
{
}
w.Close();
In this sample, the 2 first occurences of ShellPropertyWriter.WriteProperty() will do exactly the same, edit the "Contributing artists" field of the file (Explorer: Right-click > Properties > Details). The third call will throw an "Access denied" exception.
Some are editable, others are not. Just need to try.
You can write to the ShellFile directly by setting the value of the properties without ShellPropertyWriter:
var shellFile = ShellFile.FromFilePath(filePath);
shellFile.Properties.System.Author.Value = new string[] { "MyTest", "Test" };
shellFile.Properties.System.Music.Artist.Value = new string[] { "MyTest", "Test" };
shellFile.Properties.System.Music.DisplayArtist.Value = "Test";
Just be aware, that to be able to edit codec-specific fields of a file, it's necessary to have the codec installed on the computer.
In the image below there is an area, which has an unknown (custom) class. That's not a Grid or a Table.
I need to be able:
to select Rows in this area
to grab a Value from each cell
The problem is since that's not a common type element - I have no idea how to google this problem or solve it myself. So far the code is following:
Process[] proc = Process.GetProcessesByName("programname");
AutomationElement window = AutomationElement.FromHandle(proc [0].MainWindowHandle);
PropertyCondition xEllist2 = new PropertyCondition(AutomationElement.ClassNameProperty, "CustomListClass", PropertyConditionFlags.IgnoreCase);
AutomationElement targetElement = window.FindFirst(TreeScope.Children, xEllist2);
I've already tried to threat this Area as a textbox, as a grid, as a combobox, but nothing solved my problem so far. Does anybody have any advice how to grab data from this area and iterate through rows?
EDIT: sorry I've made a wrong assumption. Actually, the header(column 1, column 2, column 3) and the "lower half" of this area are different control-types!!
Thanks to Wininspector I was able to dig more information regarding these control types:
The header has following properties: HeaderControl 0x056407DC (90441692) Atom: #43288 0xFFFFFFFF (-1)
and the lower half has these: ListControl 0x056408A4 (90441892) Atom: #43288 0x02A6FDA0 (44498336)
The code that I've showed earlier - retrieved the "List" element only, so here is the update:
Process[] proc = Process.GetProcessesByName("programname");
AutomationElement window = AutomationElement.FromHandle(proc [0].MainWindowHandle);
//getting the header
PropertyCondition xEllist3 = new PropertyCondition(AutomationElement.ClassNameProperty, "CustomHeaderClass", PropertyConditionFlags.IgnoreCase);
AutomationElement headerEl = XElAE.FindFirst(TreeScope.Children, xEllist3);
//getting the list
PropertyCondition xEllist2 = new PropertyCondition(AutomationElement.ClassNameProperty, "CustomListClass", PropertyConditionFlags.IgnoreCase);
AutomationElement targetElement = window.FindFirst(TreeScope.Children, xEllist2);
After giving it a further thought I've tried to get all column names:
AutomationElementCollection headerLines = headerEl.FindAll(TreeScope.Children, new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.HeaderItem));
string headertest = headerLines[0].GetCurrentPropertyValue(AutomationElement.NameProperty) as string;
textBox2.AppendText("Header 1: " + headertest + Environment.NewLine);
Unfortunately in debug mode element count in "headerLines" is 0 so the program throws an error.
Edit 2: Thanks to the answer below - I've installed Unmanaged UI Automation, which holds better possibilities than the default UIA. http://uiacomwrapper.codeplex.com/
How do you use the legacy pattern to grab data from unknown control-type?
if((bool)datagrid.GetCurrentPropertyValue(AutomationElementIdentifiers.IsLegacyIAccessiblePatternAvailableProperty))
{
var pattern = ((LegacyIAccessiblePattern)datagrid.GetCurrentPattern(LegacyIAccessiblePattern.Pattern));
var state = pattern.Current.State;
}
Edit 3. IUIAutoamtion approach (non-working as of now)
_automation = new CUIAutomation();
cacheRequest = _automation.CreateCacheRequest();
cacheRequest.AddPattern(UiaConstants.UIA_LegacyIAccessiblePatternId);
cacheRequest.AddProperty(UiaConstants.UIA_LegacyIAccessibleNamePropertyId);
cacheRequest.TreeFilter = _automation.ContentViewCondition;
trueCondition = _automation.CreateTrueCondition();
Process[] ps = Process.GetProcessesByName("program");
IntPtr hwnd = ps[0].MainWindowHandle;
IUIAutomationElement elementMailAppWindow = _automation.ElementFromHandle(hwnd);
List<IntPtr> ls = new List<IntPtr>();
ls = GetChildWindows(hwnd);
foreach (var child in ls)
{
IUIAutomationElement iuiae = _automation.ElementFromHandle(child);
if (iuiae.CurrentClassName == "CustomListClass")
{
var outerArayOfStuff = iuiae.FindAllBuildCache(interop.UIAutomationCore.TreeScope.TreeScope_Children, trueCondition, cacheRequest.Clone());
var outerArayOfStuff2 = iuiae.FindAll(interop.UIAutomationCore.TreeScope.TreeScope_Children, trueCondition);
var countOuter = outerArayOfStuff.Length;
var countOuter2 = outerArayOfStuff2.Length;
var uiAutomationElement = outerArayOfStuff.GetElement(0); // error
var uiAutomationElement2 = outerArayOfStuff2.GetElement(0); // error
//...
//I've erased what's followed next because the code isn't working even now..
}
}
The code was implemented thanks to this issue:
Read cell Items from data grid in SysListView32 of another application using C#
As the result:
countOuter and countOuter2 lengths = 0
impossible to select elements (rows from list)
impossible to get ANY value
nothing is working
You might want to try using the core UI automation classes. It requires that you import the dll to use it in C#. Add this to your pre-build event (or do it just once, etc):
"%PROGRAMFILES%\Microsoft SDKs\Windows\v7.0A\bin\tlbimp.exe" %windir%\system32\UIAutomationCore.dll /out:..\interop.UIAutomationCore.dll"
You can then use the IUIAutomationLegacyIAccessiblePattern.
Get the constants that you need for the calls from:
C:\Program Files\Microsoft SDKs\Windows\v7.1\Include\UIAutomationClient.h
I am able to read Infragistics Ultragrids this way.
If that is too painful, try using MSAA. I used this project as a starting point with MSAA before converting to all UIA Core: MSSA Sample Code
----- Edited on 6/25/12 ------
I would definitely say that finding the proper 'identifiers' is the most painful part of using the MS UIAutomation stuff. What has helped me very much is to create a simple form application that I can use as 'location recorder'. Essentially, all you need are two things:
a way to hold focus even when you are off of your form's window Holding focus
a call to ElementFromPoint() using the x,y coordinates of where the mouse is. There is an implementation of this in the CUIAutomation class.
I use the CTRL button to tell my app to grab the mouse coordinates (System.Windows.Forms.Cursor.Position). I then get the element from the point and recursively get the element's parent until I reach the the desktop.
var desktop = auto.GetRootElement();
var walker = GetRawTreeWalker();
while (true)
{
element = walker.GetParentElement(element);
if (auto.CompareElements(desktop, element) == 1){ break;}
}
----- edit on 6/26/12 -----
Once you can recursively find automation identifiers and/or names, you can rather easily modify the code here: http://blog.functionalfun.net/2009/06/introduction-to-ui-automation-with.html to be used with the Core UI Automation classes. This will allow you to build up a string as you recurse which can be used to identify a control nested in an application with an XPath style syntax.
I'm trying to find out how to read/write to the extended file properties in C#
e.g. Comment, Bit Rate, Date Accessed, Category etc that you can see in Windows explorer.
Any ideas how to do this?
EDIT: I'll mainly be reading/writing to video files (AVI/DIVX/...)
For those of not crazy about VB, here it is in c#:
Note, you have to add a reference to Microsoft Shell Controls and Automation from the COM tab of the References dialog.
public static void Main(string[] args)
{
List<string> arrHeaders = new List<string>();
Shell32.Shell shell = new Shell32.Shell();
Shell32.Folder objFolder;
objFolder = shell.NameSpace(#"C:\temp\testprop");
for( int i = 0; i < short.MaxValue; i++ )
{
string header = objFolder.GetDetailsOf(null, i);
if (String.IsNullOrEmpty(header))
break;
arrHeaders.Add(header);
}
foreach(Shell32.FolderItem2 item in objFolder.Items())
{
for (int i = 0; i < arrHeaders.Count; i++)
{
Console.WriteLine(
$"{i}\t{arrHeaders[i]}: {objFolder.GetDetailsOf(item, i)}");
}
}
}
Solution 2016
Add following NuGet packages to your project:
Microsoft.WindowsAPICodePack-Shell by Microsoft
Microsoft.WindowsAPICodePack-Core by Microsoft
Read and Write Properties
using Microsoft.WindowsAPICodePack.Shell;
using Microsoft.WindowsAPICodePack.Shell.PropertySystem;
string filePath = #"C:\temp\example.docx";
var file = ShellFile.FromFilePath(filePath);
// Read and Write:
string[] oldAuthors = file.Properties.System.Author.Value;
string oldTitle = file.Properties.System.Title.Value;
file.Properties.System.Author.Value = new string[] { "Author #1", "Author #2" };
file.Properties.System.Title.Value = "Example Title";
// Alternate way to Write:
ShellPropertyWriter propertyWriter = file.Properties.GetPropertyWriter();
propertyWriter.WriteProperty(SystemProperties.System.Author, new string[] { "Author" });
propertyWriter.Close();
Important:
The file must be a valid one, created by the specific assigned software. Every file type has specific extended file properties and not all of them are writable.
If you right-click a file on desktop and cannot edit a property, you wont be able to edit it in code too.
Example:
Create txt file on desktop, rename its extension to docx. You can't
edit its Author or Title property.
Open it with Word, edit and save
it. Now you can.
So just make sure to use some try catch
Further Topic:
Microsoft Docs: Implementing Property Handlers
There's a CodeProject article for an ID3 reader. And a thread at kixtart.org that has more information for other properties. Basically, you need to call the GetDetailsOf() method on the folder shell object for shell32.dll.
This sample in VB.NET reads all extended properties:
Sub Main()
Dim arrHeaders(35)
Dim shell As New Shell32.Shell
Dim objFolder As Shell32.Folder
objFolder = shell.NameSpace("C:\tmp")
For i = 0 To 34
arrHeaders(i) = objFolder.GetDetailsOf(objFolder.Items, i)
Next
For Each strFileName In objfolder.Items
For i = 0 To 34
Console.WriteLine(i & vbTab & arrHeaders(i) & ": " & objfolder.GetDetailsOf(strFileName, i))
Next
Next
End Sub
You have to add a reference to Microsoft Shell Controls and Automation from the COM tab of the References dialog.
Thank you guys for this thread! It helped me when I wanted to figure out an exe's file version. However, I needed to figure out the last bit myself of what is called Extended Properties.
If you open properties of an exe (or dll) file in Windows Explorer, you get a Version tab, and a view of Extended Properties of that file. I wanted to access one of those values.
The solution to this is the property indexer FolderItem.ExtendedProperty and if you drop all spaces in the property's name, you'll get the value. E.g. File Version goes FileVersion, and there you have it.
Hope this helps anyone else, just thought I'd add this info to this thread. Cheers!
GetDetailsOf() Method - Retrieves details about an item in a folder. For example, its size, type, or the time of its last modification. File Properties may vary based on the Windows-OS version.
List<string> arrHeaders = new List<string>();
Shell shell = new ShellClass();
Folder rFolder = shell.NameSpace(_rootPath);
FolderItem rFiles = rFolder.ParseName(filename);
for (int i = 0; i < short.MaxValue; i++)
{
string value = rFolder.GetDetailsOf(rFiles, i).Trim();
arrHeaders.Add(value);
}
Jerker's answer is little simpler. Here's sample code which works from MS:
var folder = new Shell().NameSpace(folderPath);
foreach (FolderItem2 item in folder.Items())
{
var company = item.ExtendedProperty("Company");
var author = item.ExtendedProperty("Author");
// Etc.
}
For those who can't reference shell32 statically, you can invoke it dynamically like this:
var shellAppType = Type.GetTypeFromProgID("Shell.Application");
dynamic shellApp = Activator.CreateInstance(shellAppType);
var folder = shellApp.NameSpace(folderPath);
foreach (var item in folder.Items())
{
var company = item.ExtendedProperty("Company");
var author = item.ExtendedProperty("Author");
// Etc.
}
After looking at a number of solutions on this thread and elsewhere
the following code was put together. This is only to read a property.
I could not get the
Shell32.FolderItem2.ExtendedProperty function to work, it is supposed
to take a string value and return the correct value and type for that
property... this was always null for me and developer reference resources were very thin.
The WindowsApiCodePack seems
to have been abandoned by Microsoft which brings us the code below.
Use:
string propertyValue = GetExtendedFileProperty("c:\\temp\\FileNameYouWant.ext","PropertyYouWant");
Will return you the value of the extended property you want as a
string for the given file and property name.
Only loops until it found the specified property - not until
all properties are discovered like some sample code
Will work on Windows versions like Windows server 2008 where you will get the error "Unable to cast COM object of type 'System.__ComObject' to interface type 'Shell32.Shell'" if just trying to create the Shell32 Object normally.
public static string GetExtendedFileProperty(string filePath, string propertyName)
{
string value = string.Empty;
string baseFolder = Path.GetDirectoryName(filePath);
string fileName = Path.GetFileName(filePath);
//Method to load and execute the Shell object for Windows server 8 environment otherwise you get "Unable to cast COM object of type 'System.__ComObject' to interface type 'Shell32.Shell'"
Type shellAppType = Type.GetTypeFromProgID("Shell.Application");
Object shell = Activator.CreateInstance(shellAppType);
Shell32.Folder shellFolder = (Shell32.Folder)shellAppType.InvokeMember("NameSpace", System.Reflection.BindingFlags.InvokeMethod, null, shell, new object[] { baseFolder });
//Parsename will find the specific file I'm looking for in the Shell32.Folder object
Shell32.FolderItem folderitem = shellFolder.ParseName(fileName);
if (folderitem != null)
{
for (int i = 0; i < short.MaxValue; i++)
{
//Get the property name for property index i
string property = shellFolder.GetDetailsOf(null, i);
//Will be empty when all possible properties has been looped through, break out of loop
if (String.IsNullOrEmpty(property)) break;
//Skip to next property if this is not the specified property
if (property != propertyName) continue;
//Read value of property
value = shellFolder.GetDetailsOf(folderitem, i);
}
}
//returns string.Empty if no value was found for the specified property
return value;
}
Here is a solution for reading - not writing - the extended properties based on what I found on this page and at help with shell32 objects.
To be clear this is a hack. It looks like this code will still run on Windows 10 but will hit on some empty properties. Previous version of Windows should use:
var i = 0;
while (true)
{
...
if (String.IsNullOrEmpty(header)) break;
...
i++;
On Windows 10 we assume that there are about 320 properties to read and simply skip the empty entries:
private Dictionary<string, string> GetExtendedProperties(string filePath)
{
var directory = Path.GetDirectoryName(filePath);
var shell = new Shell32.Shell();
var shellFolder = shell.NameSpace(directory);
var fileName = Path.GetFileName(filePath);
var folderitem = shellFolder.ParseName(fileName);
var dictionary = new Dictionary<string, string>();
var i = -1;
while (++i < 320)
{
var header = shellFolder.GetDetailsOf(null, i);
if (String.IsNullOrEmpty(header)) continue;
var value = shellFolder.GetDetailsOf(folderitem, i);
if (!dictionary.ContainsKey(header)) dictionary.Add(header, value);
Console.WriteLine(header +": " + value);
}
Marshal.ReleaseComObject(shell);
Marshal.ReleaseComObject(shellFolder);
return dictionary;
}
As mentioned you need to reference the Com assembly Interop.Shell32.
If you get an STA related exception, you will find the solution here:
Exception when using Shell32 to get File extended properties
I have no idea what those properties names would be like on a foreign system and couldn't find information about which localizable constants to use in order to access the dictionary. I also found that not all the properties from the Properties dialog were present in the dictionary returned.
BTW this is terribly slow and - at least on Windows 10 - parsing dates in the string retrieved would be a challenge so using this seems to be a bad idea to start with.
On Windows 10 you should definitely use the Windows.Storage library which contains the SystemPhotoProperties, SystemMusicProperties etc.
https://learn.microsoft.com/en-us/windows/uwp/files/quickstart-getting-file-properties
And finally, I posted a much better solution that uses WindowsAPICodePack there
I'm not sure what types of files you are trying to write the properties for but taglib-sharp is an excellent open source tagging library that wraps up all this functionality nicely. It has a lot of built in support for most of the popular media file types but also allows you to do more advanced tagging with pretty much any file.
EDIT: I've updated the link to taglib sharp. The old link no longer worked.
EDIT: Updated the link once again per kzu's comment.
We have written an add-on for Outlook that files emails into our CRM system. Int he process of this, it saves the Outlook Message ID as a UserField on the Message itself.
eg.
currentUserProperty = Constants.APPLICATION_NAME + "EntryID";
mailItem.UserProperties.Add(currentUserProperty,
Microsoft.Office.Interop.Outlook.OlUserPropertyType.olText,
Missing.Value,
Missing.Value).Value = entryId;
Unfortunately, this is a HUUUGGEE number, much like:
"00000000D502D779150E2F4580B1AADDF04ECDA6070097EF5A1237597748A4B4F9BFF540020800000006E9E4000068BB5B6DFC36924FAEC709A17D056583000002DE0E350000"
The problem is that when the user prints the message off, Outlook insists on including this field (beneath the From/To) and because it has no spaces, cannot wrap the ID and compresses the A4 page until it can fit on horizontally. This produces teeny-tiny email printouts.
Is there any way I can correct this? I had thought of overwriting the field OriginalEntryID (which is the one causing the problem) with one delimited by spaces but I get an exception from the COM layer. My next stop is to try and suppress the output of this and other user-defined fields on Outlook stationary.
Does anyone know how this can be achieved?
You must use .NET Reflection to fix this (as recommended by Microsoft Support). Hopefully this will be fixed in future versions of the VSTO SDK.
Suppress Outlook User Field Printing
static void SuppressUserPropertyPrinting(Outlook.MailItem message)
{
try
{ // Late Binding in .NET: https://support.microsoft.com/en-us/kb/302902
Type userPropertyType;
long dispidMember = 107;
long ulPropPrintable = 0x4; // removes PDO_PRINT_SAVEAS
string dispMemberName = String.Format("[DispID={0}]", dispidMember);
object[] dispParams;
if (message.UserProperties.Count == 0) return; // no props found (exit)
// marks all user properties as suppressed
foreach (Outlook.UserProperty userProperty in message.UserProperties.Cast<Outlook.UserProperty>())
{
if (userProperty == null) continue; // no prop found (go to next)
userPropertyType = userProperty.GetType(); // user property type
// Call IDispatch::Invoke to get the current flags
object flags = userPropertyType.InvokeMember(dispMemberName, BindingFlags.GetProperty, null, userProperty, null);
long lFlags = long.Parse(flags.ToString()); // default is 45 - PDO_IS_CUSTOM|PDO_PRINT_SAVEAS|PDO_PRINT_SAVEAS_DEF (ref: http://msdn.microsoft.com/en-us/library/ee415114.aspx)
// Remove the hidden property Printable flag
lFlags &= ~ulPropPrintable; // change to 41 - // PDO_IS_CUSTOM|PDO_PRINT_SAVEAS_DEF (ref: http://msdn.microsoft.com/en-us/library/ee415114.aspx)
// Place the new flags property into an argument array
dispParams = new object[] { lFlags };
// Call IDispatch::Invoke to set the current flags
userPropertyType.InvokeMember(dispMemberName, BindingFlags.SetProperty, null, userProperty, dispParams);
}
}
catch { } // safely ignore if property suppression doesn't work
}
I think this might be able to help me:
http://www.add-in-express.com/forum/read.php?FID=5&TID=5071#postform