I built an application in C# that copies documents from a source NSF to a destination NSF. The destination NSF is an empty shell, retaining all design elements, based on the source NSF. I am using Lotus Notes 8.5.3 and am not connected to a Domino Server.
I use this application to split the source NSF into smaller chunks. The goal is to create destination NSFs that can be handled effectively by our automated (eDiscovery) systems. I need to ensure that as much metadata as possible are preserved.
My existing code meets these goals, except that that
(1) I lose foldering information. After copying documents, all folders are empty.
(2) All documents are marked as Read, even if they were unread in the source.
Code C#
//Establish session
NotesSession ns = new Domino.NotesSessionClass();
ns.Initialize("");
//Open source NSF
NotesDatabase nd = ns.GetDatabase("", "test.nsf", false);
//Open destination NSF.
//Assume that all design elements of nd2 are identical to those of nd
NotesDatabase nd2 = ns.GetDatabase("", "test2.nsf", false);
//Create view that returns all documents.
NotesView nView2 = nd.GetView("$All");
nd.CreateView("All-DR", "SELECT #ALL", nView2, false);
NotesView nView = NotesConnectionDatabase.GetView("All-DR");
//Loop through entries in the new view
NotesViewEntry nvec = nView.AllEntries;
nve = nvec.GetFirstEntry();
for (int j = 1; j <= intEntryCount; j++)
{
if (j == 1)
{
nve = nvec.GetFirstEntry();
}
else
{
nve = nvec.GetNextEntry(nve);
}
//Copy document to second database.
NotesDocument ndoc = nd.GetDocumentByUNID(nve.UniversalID);
ndoc.CopyToDatabase(nd2);
}
//End loop.
//All documents are copied.
The result is that I end up with a destination NSF that has all the documents copied over. Assume that all the folders are also there. However, none of the documents are in the folders. Every document is marked as read.
How can I fix the folders and unread issue?
There is a FolderReferences property in the NotesDocument class in the back-end classes. I'm not 100% sure if that property is exposed in the COM classes and interop for C#, but if it is, you can use that along with the PutInFolder() method to solve part of your problem.
As far as read/unread marks are concerned, the critical question is whether you are concerned only about the read/unread status for yourself, or whether you are trying to preserve it for all users of the database. If you only care about unread marks for yourself, then you might be able to use the getAllUnreadDocuments() method of the NotesDatabase class -- but this requires Notes/Domino 8 or above (on the machine where your code is running), and again I'm not sure if this method (or the NotesNoteCollection class that it returns) is exposed via the COM/interop interface for C#. If it is available, then you can iterate through the collection and use the MarkUnread() method. If you care about unread marks for all users, then I'm not sure if there is a way to do it at all -- but if there is, it's going to require using calls from the Notes C API.
Another thought on moving to folders, especially if the database isn't set up for FolderReferences to work:
You can iterate over the array of NotesView objects obtained from the NotesDatabase object's Views property. Each NotesView has a property that tells you if it is a folder.
Once you know about all the folders, you can iterate within each folder and collect a list of NotesDocuments that are contained within. Then by storing this information in a dictionary you could use this as a lookup while you process each document to decide what folder(s) it needs to be placed in.
Something like this (not tested):
object oViews = nd.Views;
object[] oarrViews = (object[])oViews;
Dictionary<string, List<string>> folderDict = new Dictionary<string, List<string>>();
for (int x=0; x < oarrViews.Length - 1; x++)
{
NotesView view = viewArray[x];
if (view.IsFolder)
{
NotesDocument doc = view.GetFirstDocument();
while (doc != null)
{
// Populate folderDict Dictionary by setting
// document's UNID as Key, and adding folder name to List
}
}
}
Then in your loop:
//Copy document to second database.
NotesDocument ndoc = nd.GetDocumentByUNID(nve.UniversalID);
NotesDocument newDoc = ndoc.CopyToDatabase(nd2);
if (folderDict.ContainsKey(nve.UniversalID)) {
foreach (var folderName in folderDict[nve.UniversalID]) {
newDoc.PutInFolder(folderName, true);
}
}
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.
Using C#, I need to pull data from a word document. I have NetOffice for word installed in the project. The data is in two parts.
First, I need to pull data from the document settings.
Second, I need to pull the content of controls in the document. The content of the fields includes checkboxes, a date, and a few paragraphs. The input method is via controls, so there must be some way to interact with the controls via the api, but I don't know how to do that.
right now, I've got the following code to pull the flat text from the document:
private static string wordDocument2String(string file)
{
NetOffice.WordApi.Application wordApplication = new NetOffice.WordApi.Application();
NetOffice.WordApi.Document newDocument = wordApplication.Documents.Open(file);
string txt = newDocument.Content.Text;
wordApplication.Quit();
wordApplication.Dispose();
return txt;
}
So the question is: how do I pull the data from the controls from the document, and how do I pull the document settings (such as the title, author, etc. as seen from word), using either NetOffice, or some other package?
I did not bother to implement NetOffice, but the commands should mostly be the same (except probably for implementation and disposal methods).
Microsoft.Office.Interop.Word.Application word = new Microsoft.Office.Interop.Word.Application();
string file = "C:\\Hello World.docx";
Microsoft.Office.Interop.Word.Document doc = word.Documents.Open(file);
// look for a specific type of Field (there are about 200 to choose from).
foreach (Field f in doc.Fields)
{
if (f.Type == WdFieldType.wdFieldDate)
{
//do something
}
}
// example of the myriad properties that could be associated with "document settings"
WdProtectionType protType = doc.ProtectionType;
if (protType.Equals(WdProtectionType.wdAllowOnlyComments))
{
//do something else
}
The MSDN reference on Word Interop is where you will find information on just about anything you need access to in a Word document.
UPDATE:
After reading your comment, here are a few document settings you can access:
string author = doc.BuiltInDocumentProperties("Author").Value;
string name = doc.Name; // this gives you the file name.
// not clear what you mean by "title"
As far as trying to understand what text you are getting from a "legacy control", I need more information as to exactly what kind of control you are extracting from. Try getting a name of the control/textbox/form/etc from within the document itself and then look up that property on the Google.
As a stab in the dark, here is an (incomplete) example of getting text from textboxes in the document:
List<string> textBoxText = new List<string>();
foreach (Microsoft.Office.Interop.Word.Shape s in doc.Shapes)
{
textBoxText.Add(s.TextFrame.TextRange.Text); //this could result in an error if there are shapes that don't contain text.
}
Another possibility is Content Controls, of which there are several types. They are often used to gather user input.
Here is some code to catch a rich text Content Control:
List<string> contentControlText = new List<string>();
foreach(ContentControl CC in doc.ContentControls)
{
if (CC.Type == WdContentControlType.wdContentControlRichText)
{
contentControlText.Add(CC.Range.Text);
}
}
Suppose I have a program which allows a user to upload any kind of file. Along with getting generic information such as file type and file size, I would like to attempt to grab any extra information (such as document properties like author, last revised, etc) that may be transported along with the document.
Since I don't have any knowledge about the incoming document/file ahead of time, I can't simply use classes that are specific to, say Microsoft Office docs. I need to do this generically and then construct a dynamic object or dictionary to hold any found key/value results.
Is this possible? If so, how? Any help is appreciated!
I found a few answers on StackOverflow for this, but none gave me a nice, clean dictionary of document properties. Here is what I finally came up with and it seems to be working nicely (you will need to reference "Microsoft Shell Controls and Automation" from the COM folder and add using Shell32; to your code:
public static Dictionary<string,string> GetDocumentMetadata(string fileName)
{
var properties = new Dictionary<string,string>();
var arrHeaders = new List<string>();
var shell = new Shell();
var objFolder = shell.NameSpace(HttpContext.Current.Server.MapPath("~/RawFiles"));
var file = objFolder.ParseName(fileName);
for (var i = 0; i < short.MaxValue; i++)
{
var header = objFolder.GetDetailsOf(null, i);
if (String.IsNullOrEmpty(header))
break;
arrHeaders.Add(header);
}
for (var i = 0; i < arrHeaders.Count; i++)
{
var value = objFolder.GetDetailsOf(file, i);
if (!String.IsNullOrEmpty(value))
{
properties.Add(arrHeaders[i], value);
}
}
return properties;
}
Is it possible to enable "Sharing" on excel documents through OpenXML or ClosedXML? Or any other library if it can help... I believe this is usually performed when you save the document (at least that's how it works in VBA), but I can't find how to specify saving arguments in C#.
I'd like to avoid using InterOp since I might batch this process on multiple files through a network.
EDIT: According to some old pages from 2009, there are limitations where OpenXML cannot operate protected files. However, would that apply to sharing too?
Sharing Excel documents using OpenXML SDK is not well documented.
I did some tests and found that it is possible to enable sharing on Excel documents
using OpenXML SDK. The following steps are necessary to enable sharing:
Add a WorkbookUserDataPart to your Excel document. Add an empty Users collection
to the part. In this collection Excel stores all users who currently have
this shared workbook open.
Add a WorkbookRevisionHeaderPart to your Excel document. Add a Headers collection
to the part. In this collection Excel will store references to history, version and revision
information. Add a first element (Header) to the collection which contains the
SheetIdMap (used for tracking revision records). In the code sample below
I've added all worksheets included in the document.
Furthermore add a WorkbookRevisionLogPart to the workbook's revision header part.
In the log part a list of revision made to the document is stored.
The code sample below shows how to enable sharing on an Excel document.
The code also checks whether sharing is already enabled on a document.
Before you enable sharing you should create a backup of your original documents.
using (SpreadsheetDocument sd = SpreadsheetDocument.Open("c:\\temp\\enable_sharing.xlsx", true))
{
WorkbookPart workbookPart = sd.WorkbookPart;
if (workbookPart.GetPartsCountOfType<WorkbookRevisionHeaderPart>() != 0)
{
Console.Out.WriteLine("Excel document already shared!");
return;
}
// Create user data part if it does not exist.
if (workbookPart.GetPartsCountOfType<WorkbookUserDataPart>() == 0)
{
Console.Out.WriteLine("Adding user data part");
WorkbookUserDataPart workbookUserDataPart = workbookPart.AddNewPart<WorkbookUserDataPart>();
Users users = new Users() { Count = (UInt32Value)0U };
users.AddNamespaceDeclaration("r", "http://schemas.openxmlformats.org/officeDocument/2006/relationships");
workbookUserDataPart.Users = users;
}
// Create revision header part and revision log part.
WorkbookRevisionHeaderPart workbookRevisonHeaderPart = workbookPart.AddNewPart<WorkbookRevisionHeaderPart>();
WorkbookRevisionLogPart workbookRevisionLogPart = workbookRevisonHeaderPart.AddNewPart<WorkbookRevisionLogPart>();
// Create empty collection of revisions.
Revisions revisions = new Revisions();
revisions.AddNamespaceDeclaration("r", "http://schemas.openxmlformats.org/officeDocument/2006/relationships");
workbookRevisionLogPart.Revisions = revisions;
string lastSetOfRevisionsGuid = Guid.NewGuid().ToString("B");
// Create headers collection (references to history, revisions)
Headers headers = new Headers() { Guid = lastSetOfRevisionsGuid };
headers.AddNamespaceDeclaration("r", "http://schemas.openxmlformats.org/officeDocument/2006/relationships");
int worksheetPartsCount = workbookPart.GetPartsCountOfType<WorksheetPart>();
// Create first element in headers collection
// which contains the SheetIdMap.
Header header = new Header() { Guid = lastSetOfRevisionsGuid, DateTime = DateTime.Now,
MaxSheetId = (UInt32Value)(uint)worksheetPartsCount+1, UserName = "hans", Id = "rId1" };
// Create the list of sheet IDs that are used for tracking
// revision records. For every worksheet in the document
// create one SheetId.
SheetIdMap sheetIdMap = new SheetIdMap() { Count = (UInt32Value)(uint)worksheetPartsCount };
for (uint i = 1; i <= worksheetPartsCount; i++)
{
SheetId sheetId = new SheetId() { Val = (UInt32Value)i };
sheetIdMap.Append(sheetId);
}
header.Append(sheetIdMap);
headers.Append(header);
workbookRevisonHeaderPart.Headers = headers;
}
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.