I'd like to read an EFS certificate (say from a pfx file) and use it temporarily to read/write some files. (I'd like it to not persist in any store once the program exits.) It looks like SetUserFileEncryptionKey might provide this functionality, but I get a bizarre return code (0x80092004) when I try it. Here's my code:
var x509Cert = new X509Certificate2(#"C:\Users\Public\Downloads\key.pfx", "<mypass>");
var certContext = Marshal.PtrToStructure<CertContext>(x509Cert.Handle);
var blob = new EfsCertificateBlob
{
dwCertEncodingType = certContext.dwCertEncodingType,
cbData = certContext.cbCertEncoded,
pbData = certContext.pbCertEncoded,
};
var pCertBlob = Marshal.AllocHGlobal(Marshal.SizeOf(blob));
Marshal.StructureToPtr(blob, pCertBlob, false);
var id = WindowsIdentity.GetCurrent();
var curStringSid = id.User?.Value;
Console.WriteLine(curStringSid);
ConvertStringSidToSid(curStringSid, out var sidPtr);
var certStruct = new EncryptionCertificate
{
cbTotalLength = (uint) Marshal.SizeOf(typeof(EncryptionCertificate)),
pUserSid = sidPtr,
pCertBlob = pCertBlob,
};
var res = SetUserFileEncryptionKey(certStruct);
Console.WriteLine($"Result: 0x{res:X}"); // Result: 0x80092004
Here too is my interop code:
[StructLayout(LayoutKind.Sequential)]
public class CertContext
{
public uint dwCertEncodingType;
public IntPtr pbCertEncoded;
public uint cbCertEncoded;
public IntPtr pCertInfo;
public IntPtr hCertStore;
}
[StructLayout(LayoutKind.Sequential)]
public class EfsCertificateBlob
{
public uint dwCertEncodingType;
public uint cbData;
public IntPtr pbData;
}
[StructLayout(LayoutKind.Sequential)]
public class EncryptionCertificate
{
public uint cbTotalLength;
public IntPtr pUserSid;
public IntPtr pCertBlob;
}
[DllImport("Advapi32.dll")]
public static extern uint SetUserFileEncryptionKey(EncryptionCertificate pEncryptionCertificate);
Does SetUserFileEncryptionKey do what I hope it does? And what am I doing wrong here?
(My use case is in working with sensitive data that I don't want the user to later be able to read or redistribute. So I'd like those files to be inaccessible as soon as the process terminates.)
It appears that 0x80092004 is CRYPT_E_NOT_FOUND and that SetUserFileEncryptionKey only works with certificates that are already part of the user's certificate store. When I import the relevant certificate, the above code returns ERROR_SUCCESS. It seems this function doesn't serve the use case that I hoped it did.
I have WCF service that is hosted on windows service. I installed this service using Windows installer. Sometimes, when i stop service using C# code, it stucks on stopping. So i thought, why not kill service if service is not stopping within 2 minutes. My code is below to stop service:
var service = ServiceController.GetServices()
.FirstOrDefault(s => s.ServiceName == serviceName);
try
{
if (service == null || service.Status != ServiceControllerStatus.Running) return;
if(service.CanStop)
{
session.LogInfo($"Stopping '{serviceName}'.");
TimeSpan timeout = TimeSpan.FromMilliseconds(ServiceStopTime);
service.Stop();
service.WaitForStatus(ServiceControllerStatus.Stopped, timeout);
session.LogInfo($"'{serviceName}' stopped successfully.");
}
It is working as expected. I want to kill my process if service does not stop. Here is my code to kill process.
var processName = GetProcessNameByWindowsService(serviceName);
if (processName == null) return;
Process[] procs = Process.GetProcessesByName(processName);
if (procs.Length > 0)
{
foreach (Process proc in procs)
{
session.LogInfo($"Killing Process'{processName}'.");
proc.Kill();
session.LogInfo($"'{processName}' killed successfully.");
}
}
It is working as expected too but the problem is when i kill the process, the service does not stop. It assigns new process to service and service keep runs. After googled and investing some time i found the cause that is the window service recovery option which is restart the service if it fails. I want to change/set the recovery option for service in case of first failure, second failure and subsequent failure to take no action using C# code. I googled but did not find anything. So i want to know how i can change the recovery option of installed window service using C#?
After investing time finally i have found the solution with the help of this link. I have written two helper classes to set/update recovery option of windows service. First of all i wrote a static helper class which is below:
using System;
using System.Runtime.InteropServices;
namespace HRTC.CustomActions.Helpers
{
public static class ServiceRecoveryOptionHelper
{
//Action Enum
public enum RecoverAction
{
None = 0, Restart = 1, Reboot = 2, RunCommand = 3
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct ServiceFailureActions
{
public int dwResetPeriod;
[MarshalAs(UnmanagedType.LPWStr)]
public string lpRebootMsg;
[MarshalAs(UnmanagedType.LPWStr)]
public string lpCommand;
public int cActions;
public IntPtr lpsaActions;
}
[StructLayout(LayoutKind.Sequential)]
public class ScAction
{
public int type;
public uint dwDelay;
}
// Win32 function to open the service control manager
[DllImport("advapi32.dll")]
public static extern IntPtr OpenSCManager(string lpMachineName, string lpDatabaseName, int dwDesiredAccess);
// Win32 function to open a service instance
[DllImport("advapi32.dll")]
public static extern IntPtr OpenService(IntPtr hScManager, string lpServiceName, int dwDesiredAccess);
// Win32 function to change the service config for the failure actions.
[DllImport("advapi32.dll", EntryPoint = "ChangeServiceConfig2")]
public static extern bool ChangeServiceFailureActions(IntPtr hService, int dwInfoLevel,
[MarshalAs(UnmanagedType.Struct)]
ref ServiceFailureActions lpInfo);
[DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true, EntryPoint = "QueryServiceConfig2W")]
public static extern Boolean QueryServiceConfig2(IntPtr hService, UInt32 dwInfoLevel, IntPtr buffer, UInt32 cbBufSize, out UInt32 pcbBytesNeeded);
[DllImport("kernel32.dll")]
public static extern int GetLastError();
}
public class FailureAction
{
// Default constructor
public FailureAction() { }
// Constructor
public FailureAction(ServiceRecoveryOptionHelper.RecoverAction actionType, int actionDelay)
{
Type = actionType;
Delay = actionDelay;
}
// Property to set recover action type
public ServiceRecoveryOptionHelper.RecoverAction Type { get; set; } = ServiceRecoveryOptionHelper.RecoverAction.None;
// Property to set recover action delay
public int Delay { get; set; }
}
}
Then i already have static class for windows services that have different methods like to start windows service, stop windows service and install service etc. I added new static method in this class to change recovery option of windows service which receive 4 parameters. First one is the service name, and other three are the recovery options of first,second and subsequent recovery options respectively. Below is it's implementation.
using System;
using System.Collections;
using System.Runtime.InteropServices;
namespace HRTC.CustomActions.Helpers
{
public class LocalServiceHelper
{
//Change service recovery option settings
private const int ServiceAllAccess = 0xF01FF;
private const int ScManagerAllAccess = 0xF003F;
private const int ServiceConfigFailureActions = 0x2;
private const int ErrorAccessDenied = 5;
public static void ChangeRevoveryOption(string serviceName, ServiceRecoveryOptionHelper.RecoverAction firstFailureAction,
ServiceRecoveryOptionHelper.RecoverAction secondFailureAction, ServiceRecoveryOptionHelper.RecoverAction thirdFailureAction)
{
try
{
// Open the service control manager
var scmHndl = ServiceRecoveryOptionHelper.OpenSCManager(null, null, ScManagerAllAccess);
if (scmHndl.ToInt32() <= 0)
return;
// Open the service
var svcHndl = ServiceRecoveryOptionHelper.OpenService(scmHndl, serviceName, ServiceAllAccess);
if (svcHndl.ToInt32() <= 0)
return;
var failureActions = new ArrayList
{
// First Failure Actions and Delay (msec)
new FailureAction(firstFailureAction, 0),
// Second Failure Actions and Delay (msec)
new FailureAction(secondFailureAction, 0),
// Subsequent Failures Actions and Delay (msec)
new FailureAction(thirdFailureAction, 0)
};
var numActions = failureActions.Count;
var myActions = new int[numActions * 2];
var currInd = 0;
foreach (FailureAction fa in failureActions)
{
myActions[currInd] = (int) fa.Type;
myActions[++currInd] = fa.Delay;
currInd++;
}
// Need to pack 8 bytes per struct
var tmpBuf = Marshal.AllocHGlobal(numActions * 8);
// Move array into marshallable pointer
Marshal.Copy(myActions, 0, tmpBuf, numActions * 2);
// Set the SERVICE_FAILURE_ACTIONS struct
var config =
new ServiceRecoveryOptionHelper.ServiceFailureActions
{
cActions = 3,
dwResetPeriod = 0,
lpCommand = null,
lpRebootMsg = null,
lpsaActions = new IntPtr(tmpBuf.ToInt32())
};
// Call the ChangeServiceFailureActions() abstraction of ChangeServiceConfig2()
var result =
ServiceRecoveryOptionHelper.ChangeServiceFailureActions(svcHndl, ServiceConfigFailureActions,
ref config);
//Check the return
if (!result)
{
var err = ServiceRecoveryOptionHelper.GetLastError();
if (err == ErrorAccessDenied)
{
throw new Exception("Access Denied while setting Failure Actions");
}
// Free the memory
Marshal.FreeHGlobal(tmpBuf);
}
}
catch (Exception)
{
throw new Exception("Unable to set service recovery options");
}
}
}
}
That's it. You just only need to call the method to change recovery option of windows service. For example:
LocalServiceHelper.ChangeRevoveryOption("ServiceName",
ServiceRecoveryOptionHelper.RecoverAction.Restart,
ServiceRecoveryOptionHelper.RecoverAction.Restart,
ServiceRecoveryOptionHelper.RecoverAction.None);
It will update the recovery option of windows service as you will mention when calling the method. Hope this help. Happy codding! :)
So I've read the documentation and countless examples online how to marshal array of structures. I've marshalled array of int's, I've marshalled structures, but now I'm completely stuck and can't get it to work no matter what I've try. Been stuck on it for over a day now.
Structure/class, tried as both
[StructLayout(LayoutKind.Sequential,CharSet = CharSet.Unicode)]
public class SaveDetails
{
[MarshalAs(UnmanagedType.LPWStr)]
public string Log;
public FILETIME FileTime;
[MarshalAs(UnmanagedType.Bool)]
public bool Saved;
}
Pinvoke and call delegate
public class LogSaveFiles : IDisposable
{
[UnmanagedFunctionPointer(CallingConvention.Winapi,CharSet = CharSet.Unicode)]
private delegate Status DLogSaveFiles([ In, Out] SaveDetails[] logsToSave, string destinationPath);
private static DLogSaveFiles _dLogSaveFiles;
private IntPtr PLogSaveFiles { get; set; }
public bool LogSaveFilesAvailable => PLogSaveFiles != IntPtr.Zero;
public LogSaveFiles(Importer importer)
{
if (importer.dllLibraryPtr!= IntPtr.Zero)
{
PLogSaveFiles = Importer.GetProcAddress(importer.dllLibrary, "LogSaveFiles");
}
}
public Status SaveFiles(SaveDetails[] logsToSave,string destinationPath)
{
Status result = Status.FunctionNotAvailable;
if (LogSaveFilesAvailable)
{
_dLogSaveFiles = (DLogSaveFiles)Marshal.GetDelegateForFunctionPointer(PLogSaveFiles, typeof(DLogSaveFiles));
result = _dLogSaveFiles(logsToSave, destinationPath);
}
return result;
}
public void Dispose()
{
}
}
Call
private void SaveLogs()
{
var logsToSave = new[]{
new SaveDetails{
FileTime = new FILETIME {dwHighDateTime = 3,dwLowDateTime = 5},
Log = LogTypes.logDeviceLog,
Saved = true},
new SaveDetails{
FileTime = new FILETIME {dwHighDateTime = 1,dwLowDateTime = 2},
Log = LogTypes.logDeviceLog,
Saved = false}
};
var pathToSave = "C:\\Logs";
_logSaveFiles.SaveFiles(logsToSave, pathToSave);
}
c++ exposed call
typedef struct _LOG_SAVE_DETAILS
{
LPTSTR szLog;
FILETIME fromFileTime;
BOOL bSaved;
} LOG_SAVE_DETAILS, *PLOG_SAVE_DETAILS;
/* Function definitions */
ULY_STATUS _API LogSaveFiles (PLOG_SAVE_DETAILS ppLogs [],
LPCTSTR szDestinationPath);
Path to destination gets passed properly, but array of structures never goes through resulting in access violation when trying to access it. At first I thought it was issue with LPTSTR not going through properly but I've implemented other calls with it on its own and succeeded marshalling it through.
I've read everything on https://learn.microsoft.com/en-us/dotnet/framework/interop/marshaling-data-with-platform-invoke , it all indicates that my approach is correct, but it doesn't work.
Any help is appreciated.
Simple solution: C side change PLOG_SAVE_DETAILS ppLogs [] to LOG_SAVE_DETAILS ppLogs [], then C#-side change public class SaveDetails to public struct SaveDetails.
Marshaling array of objects seems to be difficult (I wasn't able to do it). Marshaling array of structs works. An alternative is to do the marshaling manually, but it is a pain.
The "pain" of manual marshaling (only modified lines of code):
[UnmanagedFunctionPointer(CallingConvention.Winapi, CharSet = CharSet.Unicode)]
private delegate Status DLogSaveFiles(IntPtr[] logsToSave, string destinationPath);
and then
public Status SaveFiles(SaveDetails[] logsToSave, string destinationPath)
{
Status result = Status.FunctionNotAvailable;
if (LogSaveFilesAvailable)
{
if (_dLogSaveFiles == null)
{
_dLogSaveFiles = (DLogSaveFiles)Marshal.GetDelegateForFunctionPointer(PLogSaveFiles, typeof(DLogSaveFiles));
}
int size = Marshal.SizeOf(typeof(SaveDetails));
IntPtr basePtr = IntPtr.Zero;
IntPtr[] ptrs = new IntPtr[logsToSave.Length + 1];
try
{
basePtr = Marshal.AllocHGlobal(size * logsToSave.Length);
for (int i = 0; i < logsToSave.Length; i++)
{
ptrs[i] = IntPtr.Add(basePtr, (i * size));
Marshal.StructureToPtr(logsToSave[i], ptrs[i], false);
}
result = _dLogSaveFiles(ptrs, destinationPath);
}
finally
{
if (basePtr != IntPtr.Zero)
{
for (int i = 0; i < logsToSave.Length; i++)
{
if (ptrs[i] != IntPtr.Zero)
{
Marshal.DestroyStructure(ptrs[i], typeof(SaveDetails));
}
}
Marshal.FreeHGlobal(basePtr);
}
}
}
return result;
}
Important: this is a marshaler C#->C++. The C++ mustn't modify the received array in any way or there will be a memory leak.
In Word/Excel you have to possibility to add Custom properties. (See Image)
Custom Properties.
As you guys can see there is the field: "Properties:", you can add any information you want there.
When you save the file and you go to the file location in the folder, you can right click -> Properties and you have all the tabs: General/Security/Details/Previous Versions. with the feature you add the tab Custom.
Now I want to get this information through coding: Custom Properties information. and extract it later to notepad.
So far i used the Shell32 but then I only get the information that is in the Details tab. I did some research and saw some possibilities with DSOfile.dll. But I want to know if there is a possibility to do this without installing other DLL?
This is my code so far with the Shell32.
static void Main(string[] args)
{
//using (StreamWriter writer = new StreamWriter(#"filepathhere"))
//{
//Console.SetOut(writer);
ReadProperties();
//}
}
static void ReadProperties()
{
List<string> arrHeaders = new List<string>();
Shell shell = new Shell();
Folder objFolder = shell.NameSpace(#"filepathhere");
FolderItem objFolderItem = objFolder.ParseName("filehere.doc");
for (int i = 0; i < short.MaxValue; i++)
{
string header = objFolder.GetDetailsOf(objFolder, i);
if (String.IsNullOrEmpty(header))
break;
arrHeaders.Add(header);
}
for ( int i = 0; i < arrHeaders.Count; i++)
{
Console.WriteLine("{0}\t{1}: {2}", i, arrHeaders[i], objFolder.GetDetailsOf(objFolderItem, i));
}
Console.ReadKey();
}
Thanks in advance!
Desu
Historically these properties were defined by the technology called "Structured Storage". The first Structured Storage implementation is the ancient (but still very alive) Compound File Format
After that, Microsoft added Structured Storage capabilities directly into NTFS. This allows you to define properties like author or title on any files (even .txt) files. Although the Explorer UI does not let you do this anymore for some reason, I think it still works programmatically.
And then, with Vista, Microsoft rebooted all that and introduced a superset of all this: the Windows Property System.
Unfortunately, there is no .NET API in the framework for all this. But Microsoft created an open source .NET library called the Windows API CodePack. So, the easiest way for you to extract properties is to add a reference to the WindowsAPICodePack Core NugetPackage and you can use it like this:
static void Main(string[] args)
{
foreach (var prop in new ShellPropertyCollection(#"mypath\myfile"))
{
Console.WriteLine(prop.CanonicalName + "=" + prop.ValueAsObject);
}
}
If you don't want to add extra DLLs, then you can extract the ShellPropertyCollection code from the WindowsAPICodePack source (Windows API Code Pack: Where is it?). It's quite a work but doable.
Another solution in your case, is to use the old Structure Storage native API. I've provided a sample that does this. Here is how you can use it:
static void Main(string[] args)
{
foreach (var prop in new StructuredStorage(#"mypath\myfile").Properties)
{
Console.WriteLine(prop.Name + "=" + prop.Value);
}
}
public sealed class StructuredStorage
{
public static readonly Guid SummaryInformationFormatId = new Guid("{F29F85E0-4FF9-1068-AB91-08002B27B3D9}");
public static readonly Guid DocSummaryInformationFormatId = new Guid("{D5CDD502-2E9C-101B-9397-08002B2CF9AE}");
public static readonly Guid UserDefinedPropertiesId = new Guid("{D5CDD505-2E9C-101B-9397-08002B2CF9AE}");
private List<StructuredProperty> _properties = new List<StructuredProperty>();
public StructuredStorage(string filePath)
{
if (filePath == null)
throw new ArgumentNullException("filePath");
FilePath = filePath;
IPropertySetStorage propertySetStorage;
int hr = StgOpenStorageEx(FilePath, STGM.STGM_READ | STGM.STGM_SHARE_DENY_NONE | STGM.STGM_DIRECT_SWMR, STGFMT.STGFMT_ANY, 0, IntPtr.Zero, IntPtr.Zero, typeof(IPropertySetStorage).GUID, out propertySetStorage);
if (hr == STG_E_FILENOTFOUND || hr == STG_E_PATHNOTFOUND)
throw new FileNotFoundException(null, FilePath);
if (hr != 0)
throw new Win32Exception(hr);
try
{
LoadPropertySet(propertySetStorage, SummaryInformationFormatId);
LoadPropertySet(propertySetStorage, DocSummaryInformationFormatId);
}
finally
{
Marshal.ReleaseComObject(propertySetStorage);
}
// for some reason we can't read this one on the same COM ref?
LoadProperties(UserDefinedPropertiesId);
}
public string FilePath { get; private set; }
public IReadOnlyList<StructuredProperty> Properties
{
get
{
return _properties;
}
}
private void LoadPropertySet(IPropertySetStorage propertySetStorage, Guid fmtid)
{
IPropertyStorage propertyStorage;
int hr = propertySetStorage.Open(fmtid, STGM.STGM_READ | STGM.STGM_SHARE_EXCLUSIVE, out propertyStorage);
if (hr == STG_E_FILENOTFOUND || hr == STG_E_ACCESSDENIED)
return;
if (hr != 0)
throw new Win32Exception(hr);
IEnumSTATPROPSTG es;
propertyStorage.Enum(out es);
if (es == null)
return;
try
{
var stg = new STATPROPSTG();
int fetched;
do
{
hr = es.Next(1, ref stg, out fetched);
if (hr != 0 && hr != 1)
throw new Win32Exception(hr);
if (fetched == 1)
{
string name = GetPropertyName(fmtid, propertyStorage, stg);
var propsec = new PROPSPEC[1];
propsec[0] = new PROPSPEC();
propsec[0].ulKind = stg.lpwstrName != null ? PRSPEC.PRSPEC_LPWSTR : PRSPEC.PRSPEC_PROPID;
IntPtr lpwstr = IntPtr.Zero;
if (stg.lpwstrName != null)
{
lpwstr = Marshal.StringToCoTaskMemUni(stg.lpwstrName);
propsec[0].union.lpwstr = lpwstr;
}
else
{
propsec[0].union.propid = stg.propid;
}
var vars = new PROPVARIANT[1];
vars[0] = new PROPVARIANT();
try
{
hr = propertyStorage.ReadMultiple(1, propsec, vars);
if (hr != 0)
throw new Win32Exception(hr);
}
finally
{
if (lpwstr != IntPtr.Zero)
{
Marshal.FreeCoTaskMem(lpwstr);
}
}
object value;
try
{
switch (vars[0].vt)
{
case VARTYPE.VT_BOOL:
value = vars[0].union.boolVal != 0 ? true : false;
break;
case VARTYPE.VT_BSTR:
value = Marshal.PtrToStringUni(vars[0].union.bstrVal);
break;
case VARTYPE.VT_CY:
value = decimal.FromOACurrency(vars[0].union.cyVal);
break;
case VARTYPE.VT_DATE:
value = DateTime.FromOADate(vars[0].union.date);
break;
case VARTYPE.VT_DECIMAL:
IntPtr dec = IntPtr.Zero;
Marshal.StructureToPtr(vars[0], dec, false);
value = Marshal.PtrToStructure(dec, typeof(decimal));
break;
case VARTYPE.VT_DISPATCH:
value = Marshal.GetObjectForIUnknown(vars[0].union.pdispVal);
break;
case VARTYPE.VT_ERROR:
case VARTYPE.VT_HRESULT:
value = vars[0].union.scode;
break;
case VARTYPE.VT_FILETIME:
value = DateTime.FromFileTime(vars[0].union.filetime);
break;
case VARTYPE.VT_I1:
value = vars[0].union.cVal;
break;
case VARTYPE.VT_I2:
value = vars[0].union.iVal;
break;
case VARTYPE.VT_I4:
value = vars[0].union.lVal;
break;
case VARTYPE.VT_I8:
value = vars[0].union.hVal;
break;
case VARTYPE.VT_INT:
value = vars[0].union.intVal;
break;
case VARTYPE.VT_LPSTR:
value = Marshal.PtrToStringAnsi(vars[0].union.pszVal);
break;
case VARTYPE.VT_LPWSTR:
value = Marshal.PtrToStringUni(vars[0].union.pwszVal);
break;
case VARTYPE.VT_R4:
value = vars[0].union.fltVal;
break;
case VARTYPE.VT_R8:
value = vars[0].union.dblVal;
break;
case VARTYPE.VT_UI1:
value = vars[0].union.bVal;
break;
case VARTYPE.VT_UI2:
value = vars[0].union.uiVal;
break;
case VARTYPE.VT_UI4:
value = vars[0].union.ulVal;
break;
case VARTYPE.VT_UI8:
value = vars[0].union.uhVal;
break;
case VARTYPE.VT_UINT:
value = vars[0].union.uintVal;
break;
case VARTYPE.VT_UNKNOWN:
value = Marshal.GetObjectForIUnknown(vars[0].union.punkVal);
break;
default:
value = null;
break;
}
}
finally
{
PropVariantClear(ref vars[0]);
}
var property = new StructuredProperty(fmtid, name, stg.propid);
property.Value = value;
_properties.Add(property);
}
}
while (fetched == 1);
}
finally
{
Marshal.ReleaseComObject(es);
}
}
private static string GetPropertyName(Guid fmtid, IPropertyStorage propertyStorage, STATPROPSTG stg)
{
if (!string.IsNullOrEmpty(stg.lpwstrName))
return stg.lpwstrName;
var propids = new int[1];
propids[0] = stg.propid;
var names = new string[1];
names[0] = null;
int hr = propertyStorage.ReadPropertyNames(1, propids, names);
if (hr == 0)
return names[0];
return null;
}
public void LoadProperties(Guid formatId)
{
IPropertySetStorage propertySetStorage;
int hr = StgOpenStorageEx(FilePath, STGM.STGM_READ | STGM.STGM_SHARE_DENY_NONE | STGM.STGM_DIRECT_SWMR, STGFMT.STGFMT_ANY, 0, IntPtr.Zero, IntPtr.Zero, typeof(IPropertySetStorage).GUID, out propertySetStorage);
if (hr == STG_E_FILENOTFOUND || hr == STG_E_PATHNOTFOUND)
throw new FileNotFoundException(null, FilePath);
if (hr != 0)
throw new Win32Exception(hr);
try
{
LoadPropertySet(propertySetStorage, formatId);
}
finally
{
Marshal.ReleaseComObject(propertySetStorage);
}
}
private const int STG_E_FILENOTFOUND = unchecked((int)0x80030002);
private const int STG_E_PATHNOTFOUND = unchecked((int)0x80030003);
private const int STG_E_ACCESSDENIED = unchecked((int)0x80030005);
private enum PRSPEC
{
PRSPEC_LPWSTR = 0,
PRSPEC_PROPID = 1
}
private enum STGFMT
{
STGFMT_ANY = 4,
}
[Flags]
private enum STGM
{
STGM_READ = 0x00000000,
STGM_READWRITE = 0x00000002,
STGM_SHARE_DENY_NONE = 0x00000040,
STGM_SHARE_DENY_WRITE = 0x00000020,
STGM_SHARE_EXCLUSIVE = 0x00000010,
STGM_DIRECT_SWMR = 0x00400000
}
// we only define what we handle
private enum VARTYPE : short
{
VT_I2 = 2,
VT_I4 = 3,
VT_R4 = 4,
VT_R8 = 5,
VT_CY = 6,
VT_DATE = 7,
VT_BSTR = 8,
VT_DISPATCH = 9,
VT_ERROR = 10,
VT_BOOL = 11,
VT_UNKNOWN = 13,
VT_DECIMAL = 14,
VT_I1 = 16,
VT_UI1 = 17,
VT_UI2 = 18,
VT_UI4 = 19,
VT_I8 = 20,
VT_UI8 = 21,
VT_INT = 22,
VT_UINT = 23,
VT_HRESULT = 25,
VT_LPSTR = 30,
VT_LPWSTR = 31,
VT_FILETIME = 64,
}
[StructLayout(LayoutKind.Explicit)]
private struct PROPVARIANTunion
{
[FieldOffset(0)]
public sbyte cVal;
[FieldOffset(0)]
public byte bVal;
[FieldOffset(0)]
public short iVal;
[FieldOffset(0)]
public ushort uiVal;
[FieldOffset(0)]
public int lVal;
[FieldOffset(0)]
public uint ulVal;
[FieldOffset(0)]
public int intVal;
[FieldOffset(0)]
public uint uintVal;
[FieldOffset(0)]
public long hVal;
[FieldOffset(0)]
public ulong uhVal;
[FieldOffset(0)]
public float fltVal;
[FieldOffset(0)]
public double dblVal;
[FieldOffset(0)]
public short boolVal;
[FieldOffset(0)]
public int scode;
[FieldOffset(0)]
public long cyVal;
[FieldOffset(0)]
public double date;
[FieldOffset(0)]
public long filetime;
[FieldOffset(0)]
public IntPtr bstrVal;
[FieldOffset(0)]
public IntPtr pszVal;
[FieldOffset(0)]
public IntPtr pwszVal;
[FieldOffset(0)]
public IntPtr punkVal;
[FieldOffset(0)]
public IntPtr pdispVal;
}
[StructLayout(LayoutKind.Sequential)]
private struct PROPSPEC
{
public PRSPEC ulKind;
public PROPSPECunion union;
}
[StructLayout(LayoutKind.Explicit)]
private struct PROPSPECunion
{
[FieldOffset(0)]
public int propid;
[FieldOffset(0)]
public IntPtr lpwstr;
}
[StructLayout(LayoutKind.Sequential)]
private struct PROPVARIANT
{
public VARTYPE vt;
public ushort wReserved1;
public ushort wReserved2;
public ushort wReserved3;
public PROPVARIANTunion union;
}
[StructLayout(LayoutKind.Sequential)]
private struct STATPROPSTG
{
[MarshalAs(UnmanagedType.LPWStr)]
public string lpwstrName;
public int propid;
public VARTYPE vt;
}
[StructLayout(LayoutKind.Sequential)]
private struct STATPROPSETSTG
{
public Guid fmtid;
public Guid clsid;
public uint grfFlags;
public System.Runtime.InteropServices.ComTypes.FILETIME mtime;
public System.Runtime.InteropServices.ComTypes.FILETIME ctime;
public System.Runtime.InteropServices.ComTypes.FILETIME atime;
public uint dwOSVersion;
}
[DllImport("ole32.dll")]
private static extern int StgOpenStorageEx([MarshalAs(UnmanagedType.LPWStr)] string pwcsName, STGM grfMode, STGFMT stgfmt, int grfAttrs, IntPtr pStgOptions, IntPtr reserved2, [MarshalAs(UnmanagedType.LPStruct)] Guid riid, out IPropertySetStorage ppObjectOpen);
[DllImport("ole32.dll")]
private static extern int PropVariantClear(ref PROPVARIANT pvar);
[Guid("0000013B-0000-0000-C000-000000000046"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
private interface IEnumSTATPROPSETSTG
{
[PreserveSig]
int Next(int celt, ref STATPROPSETSTG rgelt, out int pceltFetched);
// rest ommited
}
[Guid("00000139-0000-0000-C000-000000000046"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
private interface IEnumSTATPROPSTG
{
[PreserveSig]
int Next(int celt, ref STATPROPSTG rgelt, out int pceltFetched);
// rest ommited
}
[Guid("00000138-0000-0000-C000-000000000046"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
private interface IPropertyStorage
{
[PreserveSig]
int ReadMultiple(uint cpspec, [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0)] PROPSPEC[] rgpspec, [Out, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0)] PROPVARIANT[] rgpropvar);
[PreserveSig]
int WriteMultiple(uint cpspec, [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0)] PROPSPEC[] rgpspec, [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0)] PROPVARIANT[] rgpropvar, uint propidNameFirst);
[PreserveSig]
int DeleteMultiple(uint cpspec, [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0)] PROPSPEC[] rgpspec);
[PreserveSig]
int ReadPropertyNames(uint cpropid, [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0)] int[] rgpropid, [Out, MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.LPWStr, SizeParamIndex = 0)] string[] rglpwstrName);
[PreserveSig]
int NotDeclared1();
[PreserveSig]
int NotDeclared2();
[PreserveSig]
int Commit(uint grfCommitFlags);
[PreserveSig]
int NotDeclared3();
[PreserveSig]
int Enum(out IEnumSTATPROPSTG ppenum);
// rest ommited
}
[Guid("0000013A-0000-0000-C000-000000000046"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
private interface IPropertySetStorage
{
[PreserveSig]
int Create([MarshalAs(UnmanagedType.LPStruct)] Guid rfmtid, [MarshalAs(UnmanagedType.LPStruct)] Guid pclsid, uint grfFlags, STGM grfMode, out IPropertyStorage ppprstg);
[PreserveSig]
int Open([MarshalAs(UnmanagedType.LPStruct)] Guid rfmtid, STGM grfMode, out IPropertyStorage ppprstg);
[PreserveSig]
int NotDeclared3();
[PreserveSig]
int Enum(out IEnumSTATPROPSETSTG ppenum);
}
}
public sealed class StructuredProperty
{
public StructuredProperty(Guid formatId, string name, int id)
{
FormatId = formatId;
Name = name;
Id = id;
}
public Guid FormatId { get; private set; }
public string Name { get; private set; }
public int Id { get; private set; }
public object Value { get; set; }
public override string ToString()
{
return Name;
}
}
You can try NPOI engine to extract properties from different Office files (doc, xls, xlsx, docx, etc). This component doesn't have any third party dependencies and you don't need Office to use it.
However, this library is a bit tricky because you need to use different types of property extractors for different type of files. Good code sample can be found in official Git Hub repository TestHPSFPropertiesExtractor.
NuGet package can be found here.
There's a set of NuGet packages called NetOffice that you can use. There are packages for each of the Office applications, plus a few core assemblies. Get NetOffice.Word and NetOffice.Excel and install them in your solution. There's a little documentation at the Codeplex site, but I had to browse the source to really understand what was going on. Here's a sample program:
using NetOffice.OfficeApi;
using System;
namespace Office_Doc_Reader
{
class Program
{
static void Main(string[] args)
{
using (var wordApp = new NetOffice.WordApi.Application())
using (var excelApp = new NetOffice.ExcelApi.Application())
{
var doc = wordApp.Documents.Open("C:\\Users\\John\\Desktop\\test.docx");
var xls = excelApp.Workbooks.Open("C:\\Users\\John\\Desktop\\test.xlsx");
var customProperties = (DocumentProperties)doc.CustomDocumentProperties;
foreach (var property in customProperties)
{
Console.WriteLine(String.Format("Name: {0}, Value: {1}, Type: {2}", property.Name, property.Value, property.Type));
}
customProperties = (DocumentProperties)xls.CustomDocumentProperties;
foreach (var property in customProperties)
{
Console.WriteLine(String.Format("Name: {0}, Value: {1}, Type: {2}", property.Name, property.Value, property.Type));
}
}
Console.ReadKey();
}
}
}
That shows the following results:
Name: Custom prop 1, Value: Text Value, Type: msoPropertyTypeString
Name: Custom prop 2, Value: 2/21/2016 12:00:00 AM, Type: msoPropertyTypeDate
Name: Custom prop 3, Value: 42, Type: msoPropertyTypeNumber
Name: Custom prop 4, Value: True, Type: msoPropertyTypeBoolean
Name: Foo, Value: abc, Type: msoPropertyTypeString
Name: Bar, Value: 1/1/1970 12:00:00 AM, Type: msoPropertyTypeDate
Name: Baz, Value: 3.14159, Type: msoPropertyTypeFloat
Name: Qux, Value: False, Type: msoPropertyTypeBoolean
For a Word and Excel file with these properties:
I've not worked with these much at all, so I won't be able to go much deeper than this. Hope it helps.
You may use OpenXml SDK from Microsoft. I believe it is able to get any info from word/excel files.
Have a look at this https://msdn.microsoft.com/en-us/library/office/hh674468.aspx also this https://msdn.microsoft.com/en-us/library/office/bb448854.aspx
or
http://openxmldeveloper.org/
There could be a more generic approach.
If your source is a ...x file, the document can be extracted via XML queries.
If you unzip the file (eg.g with 7-Zip) you will find a file called custom.xml in the subfolder docProps.
The properties can easily be found within the XML file
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Properties xmlns="http://schemas.openxmlformats.org/officeDocument/2006/custom-properties" xmlns:vt="http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes">
<property fmtid="{D5CDD505-2E9C-101B-9397-08002B2CF9AE}" pid="2" name="SAPDOKNR">
<vt:lpwstr>10002210058</vt:lpwstr>
</property>
...
I am trying to turn a pointer from a c dll into its equivelant C# struct array.
C Code
RECORD locked[MAX+1]; //MAX is a constant
typedef struct
{
State state; //enum
unsigned long allocated;
unsigned long lastUsed;
unsigned int useCount;
} RECORD;
API RECORD* __stdcall GetLocks( char* password )
{
if(strcmp(password, secret) == 0)
return locked;
else
return 0;
}
C# Code
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] // Pretty sure CharSet isnt actually needed here
public struct RECORD
{
public State state;
public UInt32 allocated;
public UInt32 lastUsed;
public UInt16 useCount;
}
[DllImport("gatewayapi.dll", CharSet = CharSet.Ansi)] // or here
static extern IntPtr GetLocks(string password);
public RECORD[] GetLocks(string password)
{
RECORD[] recs = new RECORD[MAX+1];
recs =(RECORD[])Marshal.PtrToStructure( GetLocks(password), typeof(RECORD[]));
if (recs.Length == 0)
{
throw new Exception();
}
return recs;
}
The above unfortunetly returns me a MissingMethodException -> No parameterless constructor defined for this object.
So in all im 100% new to Marshalling and would appreciate some advice on how to turn the pointer I receive from C into the actual C# struct array it represents.
Thanks
Given the originally posted C code, here is the answer I came up with that doesn't require compiling the code in unsafe mode:
[DllImport("gatewayapi.dll", CharSet = CharSet.Ansi)]
static extern IntPtr AMTgetLocks(string password);
public RECORD[] GetLocks(string password)
{
var channels = new RECORD[MAXCHANS + 1];
try
{
var c = AMTgetLocks(password);
var crSize = Marshal.SizeOf(typeof(RECORD));
for (int i = 0; i < MAXCHANS + 1; i++)
{
channels[i] = (CHANNELRECORD)Marshal.PtrToStructure(c, typeof(RECORD));
c = new IntPtr(c.ToInt64() + crSize);
}
}
catch (Exception)
{
throw new Exception();
}
if (channels.Length == 0)
{
throw new Exception();
}
return channels;
}