1.)
How do Load, Edit and Save binary Hive files for registry from C#?
I found this Win32 api.
http://msdn.microsoft.com/en-us/library/ee210770%28VS.85%29.aspx
This guy shared the code to dump the content of binary Hive files to text.
http://www.codeproject.com/KB/recipes/RegistryDumper.aspx
2.)
In addition to manipulating the Hive files, I also search for a method to load the Hive file into registry at runtime using C#
(similar to the Load Hive and Unload Hive commands on the File many in regedit)
/Thanks
Have you looked at the Registry and RegistryKey classes in Microsoft.Win32?
http://msdn.microsoft.com/en-us/library/microsoft.win32.aspx
It sounds like you may need to create your own representation to read the hive file and either queue up or immediately make the corresponding registry changes. Likewise you would need to write your own converter back to disk.
The article below explains how to analyze the registry file without using WinAPI (advapi32.dll). In this particular case the guy is using Mono:
http://volatile-minds.blogspot.com/2011/01/analyzing-windows-nt-registry-without.html
using (FileStream fs = File.OpenRead (path)) {
var data = new byte[checked((int)fs.Length)];
int i = 0;
int read;
using (var ms = new MemoryStream (checked((int)fs.Length))) {
while ((read = fs.Read (data, 0, data.Length)) > 0) {
ms.Write (data, 0, read);
i += read;
}
byte[] hive = ms.ToArray ();
char[] cList = new char[fs.Length];
i = 0;
foreach (byte b in hive)
cList[i++] = (char)b;
string d = new string (cList);
int all = 0;
foreach (Match mx in lf.Matches (d)) { //you can change out the regex you want here.
byte[] bb = new byte[mx.Value.Length];
char[] cb = new char[mx.Value.Length];
for (int k = 0; k < mx.Value.Length; k++) {
bb[k] = (byte)mx.Value[k];
cb[k] = (char)bb[k];
}
all++;
//Console.WriteLine (new string (cb));
}
Console.WriteLine (all.ToString ());
all = 0;
}
}
This is 9 years old, but I figured this could help someone else. I wrote this class that allows you to do something like this:
Hive.AcquirePrivileges() // Acquires the privileges necessary for loading the hive
Hive myregistryhive = Hive.LoadFromFile("hivepathhere") // Loads the hive
// use myregistryhive.RootKey (a RegistryKey), read and/or write to it and its sub keys
myregistryhive.SaveAndUnload() // Unloads the hive
Hive.ReturnPrivileges() // De-elevate back to normal privileges.
The code for the class:
class Hive
{
[DllImport("advapi32.dll", SetLastError = true)]
static extern int RegLoadKey(IntPtr hKey, string lpSubKey, string lpFile);
[DllImport("advapi32.dll", SetLastError = true)]
static extern int RegSaveKey(IntPtr hKey, string lpFile, uint securityAttrPtr = 0);
[DllImport("advapi32.dll", SetLastError = true)]
static extern int RegUnLoadKey(IntPtr hKey, string lpSubKey);
[DllImport("ntdll.dll", SetLastError = true)]
static extern IntPtr RtlAdjustPrivilege(int Privilege, bool bEnablePrivilege, bool IsThreadPrivilege, out bool PreviousValue);
[DllImport("advapi32.dll")]
static extern bool LookupPrivilegeValue(string lpSystemName, string lpName, ref UInt64 lpLuid);
[DllImport("advapi32.dll")]
static extern bool LookupPrivilegeValue(IntPtr lpSystemName, string lpName, ref UInt64 lpLuid);
private RegistryKey parentKey;
private string name;
private string originalPath;
public RegistryKey RootKey;
private Hive() { }
public static Hive LoadFromFile(string Path)
{
Hive result = new Hive();
result.parentKey = RegistryKey.OpenBaseKey(RegistryHive.Users, RegistryView.Default);
result.name = Guid.NewGuid().ToString();
result.originalPath = Path;
IntPtr parentHandle = result.parentKey.Handle.DangerousGetHandle();
RegLoadKey(parentHandle, result.name, Path);
//Console.WriteLine(Marshal.GetLastWin32Error());
result.RootKey = result.parentKey.OpenSubKey(result.name, true);
return result;
}
public static void AcquirePrivileges()
{
ulong luid = 0;
bool throwaway;
LookupPrivilegeValue(IntPtr.Zero, "SeRestorePrivilege", ref luid);
RtlAdjustPrivilege((int)luid, true, false, out throwaway);
LookupPrivilegeValue(IntPtr.Zero, "SeBackupPrivilege", ref luid);
RtlAdjustPrivilege((int)luid, true, false, out throwaway);
}
public static void ReturnPrivileges()
{
ulong luid = 0;
bool throwaway;
LookupPrivilegeValue(IntPtr.Zero, "SeRestorePrivilege", ref luid);
RtlAdjustPrivilege((int)luid, false, false, out throwaway);
LookupPrivilegeValue(IntPtr.Zero, "SeBackupPrivilege", ref luid);
RtlAdjustPrivilege((int)luid, false, false, out throwaway);
}
public void SaveAndUnload()
{
RootKey.Close();
RegUnLoadKey(parentKey.Handle.DangerousGetHandle(), name);
parentKey.Close();
}
}
Edit: Note that this requires administrator privileges.
please see: https://github.com/brandonprry/volatile_reader
It reads offline hives in C# with a GTK interface. No write support yet though.
Related
I have a service that makes use of MemoryMappedFiles for interprocess communication. It has worked great for many years and was developed in .NET Framework 4.6.1. Now comes the time to port the code to .NET 6. I've gotten the bulk of it to work correctly except for one issue: the security ACL for the memory mapped file. That argument seems to have disappeared in .NET 6.
Here is a snippet from the 4.6.1 Framework version
fs = new FileStream(FileName, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.ReadWrite);
FileSecurity fSec = File.GetAccessControl(FileName);
fSec.AddAccessRule(new FileSystemAccessRule("everyone", FileSystemRights.FullControl, AccessControlType.Allow));
File.SetAccessControl(FileName, fSec);
if (fs.Length == 0)
fs.SetLength(_SectionSize);
long fLen = fs.Length;
MemoryMappedFileSecurity security = new MemoryMappedFileSecurity();
security.AddAccessRule(new AccessRule<MemoryMappedFileRights>("everyone", MemoryMappedFileRights.FullControl, AccessControlType.Allow));
//Name = #"Global\DCCCache"; // "Global\" when running as a service so session 0 stuff available to everyone
_MMFHandle = MemoryMappedFile.CreateFromFile(fs, Name, _SectionSize, MemoryMappedFileAccess.ReadWrite, security, HandleInheritability.Inheritable, false);
_VAHandle = _MMFHandle.CreateViewAccessor();
This all works and allows non-admin user processes access to the memory mapped file.
.NET 6 drops the security argument from the .CreateFromFile method. As a result, only processes running with Administrator privileges have access to the memory mapped file. An "Access Denied" IO exception is thrown from the OpenExisting method of MemoryMappedFile for non-admin processes.
Is there a way to modify the security when I create a memory mapped file so non-admin processes have access?
I did a workaround. Put together the call I needed to create the memory mapped file with the correct security and call it once in the service that owns it. I changed the mainline code to use OpenExisting after the call to the routine below so the remainder of the code can use the .NET 6 library as intended. Not ideal, but it fixed the issue.
using System.ComponentModel;
using System.Runtime.InteropServices;
using Microsoft.Win32.SafeHandles;
namespace MMFService {
internal class MMFNet6Shim : IDisposable {
private bool win32Result = false;
private int cbSid = SECURITY_MAX_SID_SIZE;
private SECURITY_ATTRIBUTES securityAttributes = new SECURITY_ATTRIBUTES();
private SafeMemoryMappedFileHandle hFile;
private const int SDDL_REVISION_1 = 1;
private const int SECURITY_MAX_SID_SIZE = 68;
private const int PAGE_READWRITE = 0x04;
private const int FILE_MAP_WRITE = 0X02;
public MMFNet6Shim(FileStream fs, string DBName) {
win32Result = ConvertStringSecurityDescriptorToSecurityDescriptor("D:(A;OICI;GA;;;WD)", SDDL_REVISION_1, out securityAttributes.lpSecurityDescriptor, IntPtr.Zero);
if (!win32Result)
throw new Exception("ConvertStringSecurityDescriptorToSecurityDescriptor", new Win32Exception(Marshal.GetLastWin32Error()));
securityAttributes.nLength = Marshal.SizeOf(securityAttributes);
securityAttributes.bInheritHandle = false;
long fLen = fs.Length;
hFile = CreateFileMapping(fs.SafeFileHandle, ref securityAttributes, PAGE_READWRITE, 0, Convert.ToInt32(fLen), DBName);
if (hFile.IsInvalid)
throw new Exception("CreateFileMapping", new Win32Exception(Marshal.GetLastWin32Error()));
}
public void Dispose() {
if(!hFile.IsInvalid)
hFile.Close();
}
[StructLayout(LayoutKind.Sequential)]
public struct SECURITY_ATTRIBUTES {
public int nLength;
public IntPtr lpSecurityDescriptor;
public bool bInheritHandle;
}
[DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern bool ConvertStringSecurityDescriptorToSecurityDescriptor
(
[In] string StringSecurityDescriptor,
[In] int StringSDRevision,
[Out] out IntPtr SecurityDescriptor,
[Out] IntPtr SecurityDescriptorSize
);
[DllImport("kernel32.dll", SetLastError = true)]
public static extern IntPtr LocalFree([In] IntPtr hMem);
[DllImport("Kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
public static extern SafeMemoryMappedFileHandle CreateFileMapping(
[In] SafeFileHandle hFile,
[In][Optional] ref SECURITY_ATTRIBUTES lpAttributes,
[In] int flProtect,
[In] int dwMaximumSizeHigh,
[In] int dwMaximumSizeLow,
[In][Optional] string lpName
);
}
}
The easiest way is to set access for Everyone:
using System.Runtime.InteropServices;
[DllImport("advapi32.dll", EntryPoint = "SetSecurityInfo", CallingConvention = CallingConvention.Winapi,
SetLastError = true, ExactSpelling = true, CharSet = CharSet.Unicode)]
private static extern uint SetSecurityInfoByHandle(SafeHandle handle, uint objectType, uint securityInformation,
byte[]? owner, byte[]? group, byte[]? dacl, byte[]? sacl);
then
mmfile = MemoryMappedFile.CreateNew("Global\\jdfghdfghsd", requiredsize, MemoryMappedFileAccess.ReadWrite);
if (SetSecurityInfoByHandle(mmfile.SafeMemoryMappedFileHandle, 1, 4, null, null, null, null) != 0)
throw new Exception("MemoryMappedFile set security failed");
Set customized access rights if required, look at SetSecurityInfo help
So, sample apps:
App1:
using System.IO.MemoryMappedFiles;
using System.Runtime.InteropServices;
[DllImport("advapi32.dll", EntryPoint = "SetSecurityInfo", CallingConvention = CallingConvention.Winapi,
SetLastError = true, ExactSpelling = true, CharSet = CharSet.Unicode)]
static extern uint SetSecurityInfoByHandle(SafeHandle handle, uint objectType, uint securityInformation,
byte[]? owner, byte[]? group, byte[]? dacl, byte[]? sacl);
var mmfile = MemoryMappedFile.CreateNew("Global\\dsfgsdfsdf", 4, MemoryMappedFileAccess.ReadWrite);
if (SetSecurityInfoByHandle(mmfile.SafeMemoryMappedFileHandle, 1, 4, null, null, null, null) != 0)
throw new Exception("Access rights set up failed");
mmfile.CreateViewAccessor(0, 4).Write(0, 234);
Console.ReadLine();
App2:
using System.IO.MemoryMappedFiles;
Console.WriteLine(MemoryMappedFile.OpenExisting("Global\\dsfgsdfsdf", MemoryMappedFileRights.ReadWrite).CreateViewAccessor(0, 4).ReadInt32(0));
Console.ReadLine();
Run app1, then app2. It should emit 234. Then press Enter for app1.
Be sure the user runs app1 is able to create the global objects (by default only administrators and services are allowed)
I have a FixedDocument that I allow the user to preview in a WPF GUI and then print to paper without showing any Windows printing dialogue, like so:
private void Print()
{
PrintQueueCollection printQueues;
using (var printServer = new PrintServer())
{
var flags = new[] { EnumeratedPrintQueueTypes.Local };
printQueues = printServer.GetPrintQueues(flags);
}
//SelectedPrinter.FullName can be something like "Microsoft Print to PDF"
var selectedQueue = printQueues.SingleOrDefault(pq => pq.FullName == SelectedPrinter.FullName);
if (selectedQueue != null)
{
var myTicket = new PrintTicket
{
CopyCount = 1,
PageOrientation = PageOrientation.Portrait,
OutputColor = OutputColor.Color,
PageMediaSize = new PageMediaSize(PageMediaSizeName.ISOA4)
};
var mergeTicketResult = selectedQueue.MergeAndValidatePrintTicket(selectedQueue.DefaultPrintTicket, myTicket);
var printTicket = mergeTicketResult.ValidatedPrintTicket;
// TODO: Make sure merge was OK
// Calling GetPrintCapabilities with our ticket allows us to use
// the OrientedPageMediaHeight/OrientedPageMediaWidth properties
// and the PageImageableArea property to calculate the minimum
// document margins supported by the printer. Very important!
var printCapabilities = queue.GetPrintCapabilities(myTicket);
var fixedDocument = GenerateFixedDocument(printCapabilities);
var dlg = new PrintDialog
{
PrintTicket = printTicket,
PrintQueue = selectedQueue
};
dlg.PrintDocument(fixedDocument.DocumentPaginator, "test document");
}
}
The problem is that I want to also support virtual/file printers, namely PDF printing, by giving the file destination path and not showing any Windows dialogues, but that doesn't seem to work with the PrintDialog.
I would really like to avoid 3rd party libraries as much as possible, so at least for now, using something like PdfSharp to convert an XPS to PDF is not something I want to do. Correction: It seems like XPS conversion support was removed from the latest version of PdfSharp.
After doing some research, it seems the only way to print straight to a file is to use a PrintDocument where it's possible to set PrintFileName and PrintToFile in the PrinterSettings object, but there is no way to give the actual document contents, rather we need to subscribe to the PrintPage event and do some System.Drawing.Graphics manipulation where the document is created.
Here's the code I tried:
var printDoc = new PrintDocument
{
PrinterSettings =
{
PrinterName = SelectedPrinter.FullName,
PrintFileName = destinationFilePath,
PrintToFile = true
},
PrintController = new StandardPrintController()
};
printDoc.PrintPage += OnPrintPage; // Without this line, we get a blank PDF
printDoc.Print();
Then the handler for PrintPage where we need to build the document:
private void OnPrintPage(object sender, PrintPageEventArgs e)
{
// What to do here?
}
Other things that I thought could work are using the System.Windows.Forms.PrintDialog class instead, but that also expects a PrintDocument. I was able to create an XPS file easily like so:
var pkg = Package.Open(destinationFilePath, FileMode.Create);
var doc = new XpsDocument(pkg);
var writer = XpsDocument.CreateXpsDocumentWriter(doc);
writer.Write(PreviewDocument.DocumentPaginator);
pkg.Flush();
pkg.Close();
But it's not a PDF, and there seems to be no way to convert it to PDF without a 3rd party library.
Is it possible to maybe do a hack that automatically fills the filename then clicks save on the PrintDialog?
Thank you!
EDIT: It's possible to print directly to PDF from Word documents using Microsoft.Office.Interop.Word, but there seems to be no easy way of converting from XPS/FixedDocument to Word.
EDIT: It seems so far the best way is to grab the old XPS to PDF conversion code that was present in PdfSharp 1.31. I grabbed the source code and built it, imported the DLL's, and it works. Credit goes to Nathan Jones, check out his blog post about this here.
Solved! After googling around I was inspired by the P/Invoke method of directly calling Windows printers.
So the solution is to use the Print Spooler API functions to directly call the Microsoft Print to PDF printer available in Windows (make sure the feature is installed though!) and giving the WritePrinter function the bytes of an XPS file.
I believe this works because the Microsoft PDF printer driver understands the XPS page description language. This can be checked by inspecting the IsXpsDevice property of the print queue.
The "Microsoft Print to PDF" feature must be installed in Windows for this to work!
Here's the code:
using System;
using System.Linq;
using System.Printing;
using System.Runtime.InteropServices;
public static class PdfFilePrinter
{
private const string PdfPrinterDriveName = "Microsoft Print To PDF";
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
private class DOCINFOA
{
[MarshalAs(UnmanagedType.LPStr)]
public string pDocName;
[MarshalAs(UnmanagedType.LPStr)]
public string pOutputFile;
[MarshalAs(UnmanagedType.LPStr)]
public string pDataType;
}
[DllImport("winspool.drv", EntryPoint = "OpenPrinterA", SetLastError = true, CharSet = CharSet.Ansi, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
private static extern bool OpenPrinter([MarshalAs(UnmanagedType.LPStr)] string szPrinter, out IntPtr hPrinter, IntPtr pd);
[DllImport("winspool.drv", EntryPoint = "ClosePrinter", SetLastError = true, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
private static extern bool ClosePrinter(IntPtr hPrinter);
[DllImport("winspool.drv", EntryPoint = "StartDocPrinterA", SetLastError = true, CharSet = CharSet.Ansi, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
private static extern int StartDocPrinter(IntPtr hPrinter, int level, [In, MarshalAs(UnmanagedType.LPStruct)] DOCINFOA di);
[DllImport("winspool.drv", EntryPoint = "EndDocPrinter", SetLastError = true, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
private static extern bool EndDocPrinter(IntPtr hPrinter);
[DllImport("winspool.drv", EntryPoint = "StartPagePrinter", SetLastError = true, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
private static extern bool StartPagePrinter(IntPtr hPrinter);
[DllImport("winspool.drv", EntryPoint = "EndPagePrinter", SetLastError = true, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
private static extern bool EndPagePrinter(IntPtr hPrinter);
[DllImport("winspool.drv", EntryPoint = "WritePrinter", SetLastError = true, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
private static extern bool WritePrinter(IntPtr hPrinter, IntPtr pBytes, int dwCount, out int dwWritten);
public static void PrintXpsToPdf(byte[] bytes, string outputFilePath, string documentTitle)
{
// Get Microsoft Print to PDF print queue
var pdfPrintQueue = GetMicrosoftPdfPrintQueue();
// Copy byte array to unmanaged pointer
var ptrUnmanagedBytes = Marshal.AllocCoTaskMem(bytes.Length);
Marshal.Copy(bytes, 0, ptrUnmanagedBytes, bytes.Length);
// Prepare document info
var di = new DOCINFOA
{
pDocName = documentTitle,
pOutputFile = outputFilePath,
pDataType = "RAW"
};
// Print to PDF
var errorCode = SendBytesToPrinter(pdfPrintQueue.Name, ptrUnmanagedBytes, bytes.Length, di, out var jobId);
// Free unmanaged memory
Marshal.FreeCoTaskMem(ptrUnmanagedBytes);
// Check if job in error state (for example not enough disk space)
var jobFailed = false;
try
{
var pdfPrintJob = pdfPrintQueue.GetJob(jobId);
if (pdfPrintJob.IsInError)
{
jobFailed = true;
pdfPrintJob.Cancel();
}
}
catch
{
// If job succeeds, GetJob will throw an exception. Ignore it.
}
finally
{
pdfPrintQueue.Dispose();
}
if (errorCode > 0 || jobFailed)
{
try
{
if (File.Exists(outputFilePath))
{
File.Delete(outputFilePath);
}
}
catch
{
// ignored
}
}
if (errorCode > 0)
{
throw new Exception($"Printing to PDF failed. Error code: {errorCode}.");
}
if (jobFailed)
{
throw new Exception("PDF Print job failed.");
}
}
private static int SendBytesToPrinter(string szPrinterName, IntPtr pBytes, int dwCount, DOCINFOA documentInfo, out int jobId)
{
jobId = 0;
var dwWritten = 0;
var success = false;
if (OpenPrinter(szPrinterName.Normalize(), out var hPrinter, IntPtr.Zero))
{
jobId = StartDocPrinter(hPrinter, 1, documentInfo);
if (jobId > 0)
{
if (StartPagePrinter(hPrinter))
{
success = WritePrinter(hPrinter, pBytes, dwCount, out dwWritten);
EndPagePrinter(hPrinter);
}
EndDocPrinter(hPrinter);
}
ClosePrinter(hPrinter);
}
// TODO: The other methods such as OpenPrinter also have return values. Check those?
if (success == false)
{
return Marshal.GetLastWin32Error();
}
return 0;
}
private static PrintQueue GetMicrosoftPdfPrintQueue()
{
PrintQueue pdfPrintQueue = null;
try
{
using (var printServer = new PrintServer())
{
var flags = new[] { EnumeratedPrintQueueTypes.Local };
// FirstOrDefault because it's possible for there to be multiple PDF printers with the same driver name (though unusual)
// To get a specific printer, search by FullName property instead (note that in Windows, queue name can be changed)
pdfPrintQueue = printServer.GetPrintQueues(flags).FirstOrDefault(lq => lq.QueueDriver.Name == PdfPrinterDriveName);
}
if (pdfPrintQueue == null)
{
throw new Exception($"Could not find printer with driver name: {PdfPrinterDriveName}");
}
if (!pdfPrintQueue.IsXpsDevice)
{
throw new Exception($"PrintQueue '{pdfPrintQueue.Name}' does not understand XPS page description language.");
}
return pdfPrintQueue;
}
catch
{
pdfPrintQueue?.Dispose();
throw;
}
}
}
Usage:
public static void FixedDocument2Pdf(FixedDocument fd)
{
// Convert FixedDocument to XPS file in memory
var ms = new MemoryStream();
var package = Package.Open(ms, FileMode.Create);
var doc = new XpsDocument(package);
var writer = XpsDocument.CreateXpsDocumentWriter(doc);
writer.Write(fd.DocumentPaginator);
doc.Close();
package.Close();
// Get XPS file bytes
var bytes = ms.ToArray();
ms.Dispose();
// Print to PDF
var outputFilePath = #"C:\tmp\test.pdf";
PdfFilePrinter.PrintXpsToPdf(bytes, outputFilePath, "Document Title");
}
In the code above, instead of directly giving the printer name, I get the name by finding the print queue using the driver name because I believe it's constant while the printer name can actually be changed in Windows, also I don't know if it's affected by localization so this way is safer.
Note: It's a good idea to check available disk space size before starting the printing operation, because I couldn't find a way to reliably find out if the error was insufficient disk space. One idea is to multiply the XPS byte array length by a magic number like 3 and then check if we have that much space on disk. Also, giving an empty byte array or one with bogus data does not fail anywhere, but produces a corrupt PDF file.
Note from comments:
Simply reading an XPS file using FileStream will not work. We have to create an XpsDocument from a Package in memory, then read the bytes from the MemomryStream like this:
public static void PrintFile(string xpsSourcePath, string pdfOutputPath)
{
// Write XPS file to memory stream
var ms = new MemoryStream();
var package = Package.Open(ms, FileMode.Create);
var doc = new XpsDocument(package);
var writer = XpsDocument.CreateXpsDocumentWriter(doc);
writer.Write(xpsSourcePath);
doc.Close();
package.Close();
// Get XPS file bytes
var bytes = ms.ToArray();
ms.Dispose();
// Print to PDF
PdfPrinter.PrintXpsToPdf(bytes, pdfOutputPath, "Document title");
}
I am trying to get the active window's name as shown in the task manager app list (using c#).
I had the same issue as described here.
I tried to do as they described but I have issue while the focused application is the picture library I get exception.
I also tried this, but nothing gives me the results I expect.
For now I use:
IntPtr handle = IntPtr.Zero;
handle = GetForegroundWindow();
const int nChars = 256;
StringBuilder Buff = new StringBuilder(nChars);
if (GetWindowText(handle, Buff, nChars) > 0)
{
windowText = Buff.ToString();
}
and delete what is not relevant based on a table I created for most common apps, but I don't like this workaround.
Is there a way to get the app name as it is in the task manager for all running app?
After reading a lot, I separated my code into two cases, for metro application and all other applications.
My solution handle the exception I got for metro applications and exceptions I got regarding the platform.
This is the code that finally worked:
[DllImport("user32.dll")]
public static extern IntPtr GetForegroundWindow();
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
static extern int GetWindowText(IntPtr hWnd, StringBuilder lpString, int nMaxCount);
[DllImport("user32.dll")]
static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);
public string GetActiveWindowTitle()
{
var handle = GetForegroundWindow();
string fileName = "";
string name = "";
uint pid = 0;
GetWindowThreadProcessId(handle, out pid);
Process p = Process.GetProcessById((int)pid);
var processname = p.ProcessName;
switch (processname)
{
case "explorer": //metro processes
case "WWAHost":
name = GetTitle(handle);
return name;
default:
break;
}
string wmiQuery = string.Format("SELECT ProcessId, ExecutablePath FROM Win32_Process WHERE ProcessId LIKE '{0}'", pid.ToString());
var pro = new ManagementObjectSearcher(wmiQuery).Get().Cast<ManagementObject>().FirstOrDefault();
fileName = (string)pro["ExecutablePath"];
// Get the file version
FileVersionInfo myFileVersionInfo = FileVersionInfo.GetVersionInfo(fileName);
// Get the file description
name = myFileVersionInfo.FileDescription;
if (name == "")
name = GetTitle(handle);
return name;
}
public string GetTitle(IntPtr handle)
{
string windowText = "";
const int nChars = 256;
StringBuilder Buff = new StringBuilder(nChars);
if (GetWindowText(handle, Buff, nChars) > 0)
{
windowText = Buff.ToString();
}
return windowText;
}
It sounds like you need to go through each top level window (direct children of the desktop window, use EnumWindows via pinvoke http://msdn.microsoft.com/en-us/library/windows/desktop/ms633497(v=vs.85).aspx) and then call your GetWindowText pinvoke function.
EnumWindows will 'Enumerates all top-level windows on the screen by passing the handle to each window, in turn, to an application-defined callback function.'
According the function description in "http://msdn.microsoft.com/en-us/library/cc196998%28v=VS.85%29.aspx", I wrote the following code to try to get IE protected cookies:
public static string GetProtectedModeCookie(string lpszURL, string lpszCookieName, uint dwFlags)
{
var size = 255;
var sb = new System.Text.StringBuilder(size);
var acturalSize = sb.Capacity;
var code = IEGetProtectedModeCookie(lpszURL, lpszCookieName, sb, ref acturalSize, dwFlags);
if ((code & 0x80000000) > 0) return string.Empty;
if (acturalSize > size)
{
sb.EnsureCapacity(acturalSize);
IEGetProtectedModeCookie(lpszURL, lpszCookieName, sb, ref acturalSize, dwFlags);
}
return sb.ToString();
}
[DllImport("ieframe.dll", SetLastError = true)]
public static extern uint IEGetProtectedModeCookie(string lpszURL, string lpszCookieName, System.Text.StringBuilder pszCookieData, ref int pcchCookieData, int dwFlags);
to test this function:
var cookies = GetProtectedModeCookie("http://bbs.pcbeta.com/", null, 0);
But the api IEGetProtectedModeCookie always return 0x80070057 which indicates that one or more arguments are invalid.
I was confused, after a lot of try finally failed, only get this result. Can anybody help me?
IEGetProtectedModeCookie will return E_INVALIDARG if it thinks that the URL isn't meant to open in Protected Mode. It determines this using the IEIsProtectedModeURL API. So if you've put that URL in the Trusted Zone or whatnot, then you'll hit this error. The underlying InternetGetCookie API will return E_INVALIDARG if you fail to pass a URL or fail to pass a pointer to an integer for the size of the buffer.
Also note that the IEGetProtectedModeCookie API will not work from a high integrity (e.g. Admin) process; it will return ERROR_INVALID_ACCESS (0x8000000C).
Here's the code I use:
[DllImport("ieframe.dll", CharSet = CharSet.Unicode, EntryPoint = "IEGetProtectedModeCookie", SetLastError = true)]
public static extern int IEGetProtectedModeCookie(String url, String cookieName, StringBuilder cookieData, ref int size, uint flag);
private void GetCookie_Click(object sender, EventArgs e)
{
int iSize = 4096;
StringBuilder sbValue = new StringBuilder(iSize);
int hResult = IEAPI.IEGetProtectedModeCookie("http://www.google.com", "PREF", sbValue, ref iSize, 0);
if (hResult == 0)
{
MessageBox.Show(sbValue.ToString());
}
else
{
MessageBox.Show("Failed to get cookie. HRESULT=0x" + hResult.ToString("x") + "\nLast Win32Error=" + Marshal.GetLastWin32Error().ToString(), "Failed");
}
}
Charset parameter must be exist in DllImport attribute. Change the API declartion to follow will works well:
[DllImport("ieframe.dll", SetLastError = true, CharSet = CharSet.Unicode)]
public static extern uint IEGetProtectedModeCookie(string lpszURL, string lpszCookieName, System.Text.StringBuilder pszCookieData, ref int pcchCookieData, uint dwFlags);
If Charset parameter missed, this API will always return 0x80070057 which indicates one or more arguments are invalid.
How can I determine if a remote drive has enough space for me to upload a given file using C# in .Net?
There are two possible solutions.
Call the Win32 function GetDiskFreeSpaceEx. Here is a sample program:
internal static class Win32
{
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
internal static extern bool GetDiskFreeSpaceEx(string drive, out long freeBytesForUser, out long totalBytes, out long freeBytes);
}
class Program
{
static void Main(string[] args)
{
long freeBytesForUser;
long totalBytes;
long freeBytes;
if (Win32.GetDiskFreeSpaceEx(#"\\prime\cargohold", out freeBytesForUser, out totalBytes, out freeBytes)) {
Console.WriteLine(freeBytesForUser);
Console.WriteLine(totalBytes);
Console.WriteLine(freeBytes);
}
}
}
Use the system management interface. There is another answer in this post which describes this. This method is really designed for use in scripting languages such as PowerShell. It performs a lot of fluff just to get the right object. Ultimately, I suspect, this method boils down to calling GetDiskFreeSpaceEx.
Anybody doing any serious Windows development in C# will probably end up calling many Win32 functions. The .NET framework just doesn't cover 100% of the Win32 API. Any large program will quickly uncover gaps in the .NET libraries that are only available through the Win32 API. I would get hold of one of the Win32 wrappers for .NET and include this in your project. This will give you instant access to just about every Win32 API.
Use WMI
using System.Management;
// Get all the network drives (drivetype=4)
SelectQuery query = new SelectQuery("select Name, VolumeName, FreeSpace from win32_logicaldisk where drivetype=4");
ManagementObjectSearcher searcher = new ManagementObjectSearcher(query);
foreach (ManagementObject drive in searcher.Get())
{
string Name = (string)drive["Name"];
string VolumeName = (string)drive["VolumeName"];
UInt64 freeSpace = (UInt64)drive["FreeSpace"];
}
based on (stolen from) http://www.dreamincode.net/code/snippet1576.htm
Are you talking about mapping a network share to a logical drive on you computer?
If so you can use DriveInfo.
DriveInfo info = new DriveInfo("X:");
info.AvailableFreeSpace;
DriveInfo only works with logical drives so if you are just using the full share (UNC) name I don't think the above code will work.
I'm not sure if GetDiskFreeSpaceEx works on UNC shares, but if it does use that, otherwise here is how to mount a UNC share to a logal drive:
EDIT GetDiskFreeSpaceEx does work on UNC shares, use that...however, this code was too much effort to just delete, and is handy if you ever want to mount a UNC share as a local drive in your code.
public class DriveWrapper
{
[StructLayout(LayoutKind.Sequential)]
public struct NETRESOURCEA
{
public int dwScope;
public int dwType;
public int dwDisplayType;
public int dwUsage;
[MarshalAs(UnmanagedType.LPStr)]
public string lpLocalName;
[MarshalAs(UnmanagedType.LPStr)]
public string lpRemoteName;
[MarshalAs(UnmanagedType.LPStr)]
public string lpComment;
[MarshalAs(UnmanagedType.LPStr)]
public string lpProvider;
public override String ToString()
{
String str = "LocalName: " + lpLocalName + " RemoteName: " + lpRemoteName
+ " Comment: " + lpComment + " lpProvider: " + lpProvider;
return (str);
}
}
[DllImport("mpr.dll")]
public static extern int WNetAddConnection2A(
[MarshalAs(UnmanagedType.LPArray)] NETRESOURCEA[] lpNetResource,
[MarshalAs(UnmanagedType.LPStr)] string lpPassword,
[MarshalAs(UnmanagedType.LPStr)] string UserName,
int dwFlags);
[DllImport("mpr.dll", CharSet = System.Runtime.InteropServices.CharSet.Auto)]
private static extern int WNetCancelConnection2A(
[MarshalAs(UnmanagedType.LPStr)]
string lpName,
int dwFlags,
int fForce
);
public int GetDriveSpace(string shareName, string userName, string password)
{
NETRESOURCEA[] n = new NETRESOURCEA[1];
n[0] = new NETRESOURCEA();
n[0].dwScope = 0;
n[0].dwType = 0;
n[0].dwDisplayType = 0;
n[0].dwUsage = 0;
n[0].dwType = 1;
n[0].lpLocalName = "x:";
n[0].lpRemoteName = shareName;
n[0].lpProvider = null;
int res = WNetAddConnection2A(n, userName, password, 1);
DriveInfo info = new DriveInfo("x:");
int space = info.AvailableFreeSpace;
int err = 0;
err = WNetCancelConnection2A("x:", 0, 1);
return space;
}
}