i am trying to implement a custom PSHost class for the purpose of crafting GUIs for Power Shell scripts.
I have taken the code here as a base and started adapting it for GUI Projects
https://msdn.microsoft.com/en-us/library/ee706557%28v=vs.85%29.aspx
All was fine until i tried to run the Get-Credential cmdlet and got a error that the method is not implemented(ooouuuu)...
The initial code was these two methods:
public override PSCredential PromptForCredential(
string caption,
string message,
string userName,
string targetName)
{
throw new NotImplementedException(
"The method or operation is not implemented.");
}
public override PSCredential PromptForCredential(
string caption,
string message,
string userName,
string targetName,
PSCredentialTypes allowedCredentialTypes,
PSCredentialUIOptions options)
{
throw new NotImplementedException(
"The method or operation is not implemented.");
}
So after some research i implemented the dialogue like this:
[DllImport("ole32.dll")]
public static extern void CoTaskMemFree(IntPtr ptr);
[DllImport("credui.dll", CharSet = CharSet.Auto)]
private static extern int CredUIPromptForWindowsCredentials(ref CREDUI_INFO notUsedHere, int authError, ref uint authPackage, IntPtr InAuthBuffer, uint InAuthBufferSize, out IntPtr refOutAuthBuffer, out uint refOutAuthBufferSize, ref bool fSave, int flags);
[DllImport("credui.dll", CharSet = CharSet.Auto)]
private static extern bool CredUnPackAuthenticationBuffer(int dwFlags, IntPtr pAuthBuffer, uint cbAuthBuffer, StringBuilder pszUserName, ref int pcchMaxUserName, StringBuilder pszDomainName, ref int pcchMaxDomainame, StringBuilder pszPassword, ref int pcchMaxPassword);
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
private struct CREDUI_INFO
{
public int cbSize;
public IntPtr hwndParent;
public string pszMessageText;
public string pszCaptionText;
public IntPtr hbmBanner;
}
private enum PromptForWindowsCredentialsFlags
{
...
}
public override PSCredential PromptForCredential(
string caption,
string message,
string userName,
string targetName,
PSCredentialTypes allowedCredentialTypes,
PSCredentialUIOptions options)
{
CREDUI_INFO credui = new CREDUI_INFO();
credui.pszCaptionText = "Please enter the credentails";
credui.pszMessageText = "DisplayedMessage";
credui.cbSize = Marshal.SizeOf(credui);
uint authPackage = 0;
IntPtr outCredBuffer = new IntPtr();
uint outCredSize;
bool save = false;
int result = CredUIPromptForWindowsCredentials(ref credui, 0, ref authPackage, IntPtr.Zero, 0, out outCredBuffer, out outCredSize, ref save, 1 /* Generic */);
var usernameBuf = new StringBuilder(100);
var passwordBuf = new StringBuilder(100);
var domainBuf = new StringBuilder(100);
int maxUserName = 100;
int maxDomain = 100;
int maxPassword = 100;
if (result == 0)
{
if (CredUnPackAuthenticationBuffer(0, outCredBuffer, outCredSize, usernameBuf, ref maxUserName, domainBuf, ref maxDomain, passwordBuf, ref maxPassword))
{
//clear the memory allocated by CredUIPromptForWindowsCredentials
CoTaskMemFree(outCredBuffer);
SecureString secureString = new SecureString();
foreach (char c in passwordBuf.ToString())
{
secureString.AppendChar(c);
}
return new PSCredential(usernameBuf.ToString(), secureString);
}
}
return null;
}
This works fine but there is one snag, when running the Get-Credential cmdlet it prompts for a value for the Credential parameter, regardless of any input after hitting return the dialogue pops up and everything works as expected.
Doing the same in ISE the dialogue pops up directly without any prompt.
I thought it was due to the arguments of the method but making them optional by defining a default value did not make a difference.
It might be due to how the cmdlet is defined in the PS environment so my question is:
How can i make it so that running the cmdlet will jump straight to the dialogue?
Is it possible to define a default value for the Credential parameter to bind to? I'm guessing this is how ISE gets around this or am i missing something?
I took some time and experimenting but i found it, the mandatory parameter binding is handled by the public override Dictionary<string, PSObject> Prompt(string caption, string message, Collection<FieldDescription> descriptions) method in the PSHostUserInterface interface before PromptForCredential is called.
Related
I want users to be able to change the display name of an existing Windows service using our configuration tool. In code, given an instance of the corresponding ServiceController object, setting its DisplayName property seems to have effect. Tried calling Refresh, which seemed to have no effect. The MSDN doc is a little unclear on what Refresh does - does it reread the current service settings or does it write my changes to the service? Code is simple:
ServiceController sc = GetServiceController(CurrentInterfaceData.ServiceName);
sc.DisplayName = "MyService";
sc.Refresh();
Use WMI. For that you have to add a reference to System.Management assembly. Here is excerpt of a working code that I had used for a generic installer:
class Program
{
static void Main(string[] args)
{
UpdateService("ipsecd");
}
private const int LOGON32_LOGON_NEW_CREDENTIALS = 9;
private const int LOGON32_PROVIDER_WINNT50 = 3;
private const int SERVICE_ALL_ACCESS = 0xF01FF;
private const int SC_MANAGER_ALL_ACCESS = 0xF003F;
private const uint SERVICE_NO_CHANGE = 0xFFFFFFFF;
private const uint SERVICE_WIN32_OWN_PROCESS = 0x00000010;
private const uint SERVICE_INTERACTIVE_PROCESS = 0x00000100;
// Win32 function to connect to remote machine
[DllImport("advapi32.dll", SetLastError = true)]
private static extern Boolean LogonUser(string lpUsername, string lpDomain, string lpPassword, int dwLogonType,
int dwLogonProvider, out IntPtr phToken);
// Win32 function to connect to impersonate user
[DllImport("advapi32.dll", SetLastError = true)]
static extern int ImpersonateLoggedOnUser(IntPtr hToken);
// Win32 function to open the service control manager
[DllImport("advapi32.dll")]
private static extern IntPtr OpenSCManager(string lpMachineName, string lpDatabaseName, int dwDesiredAccess);
// Win32 function to open a service instance
[DllImport("advapi32.dll")]
private static extern IntPtr OpenService(IntPtr hSCManager, string lpServiceName, int dwDesiredAccess);
// Win32 function to change the service config
[DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern Boolean ChangeServiceConfig(
IntPtr hService,
UInt32 nServiceType,
UInt32 nStartType,
UInt32 nErrorControl,
String lpBinaryPathName,
String lpLoadOrderGroup,
IntPtr lpdwTagId,
[In] char[] lpDependencies,
String lpServiceStartName,
String lpPassword,
String lpDisplayName);
public static void UpdateService(string serviceName)
{
ManagementScope scope = null;
ObjectQuery filter = new ObjectQuery(string.Format("SELECT * FROM Win32_Service WHERE Name = '{0}'", serviceName));
ManagementObjectSearcher query = new ManagementObjectSearcher(scope, filter);
try
{
ManagementObjectCollection services = query.Get();
// No match = failed condition
if (services.Count == 0)
return;
foreach (ManagementObject service in services)
{
SetServiceDisplayName(serviceName, "new disp name");
service.Dispose();
}
}
catch (Exception ex)
{
//Could not set property
}
}
private static void SetServiceDisplayName(string name, string dispName)
{
IntPtr svcHndl = OpenService("", "", name);
// Call the ChangeServiceFailureActions() abstraction of ChangeServiceConfig2()
bool rslt = ChangeServiceConfig(svcHndl, SERVICE_NO_CHANGE,
SERVICE_NO_CHANGE, SERVICE_NO_CHANGE, null, null, IntPtr.Zero, null, null, null, dispName);
}
private static IntPtr OpenService(string userName, string password, string serviceName)
{
IntPtr scmHndl = IntPtr.Zero;
IntPtr svcHndl = IntPtr.Zero;
// Open the service control manager
scmHndl = OpenSCManager("", null, SC_MANAGER_ALL_ACCESS);
if (scmHndl.ToInt32() <= 0)
return IntPtr.Zero;
// Open the service
svcHndl = OpenService(scmHndl, serviceName, SERVICE_ALL_ACCESS);
if (svcHndl.ToInt32() <= 0)
return IntPtr.Zero;
return svcHndl;
}
}
Try it. It should work.
I believe the name of the Windows Service is set when the service is installed. I don't think that changing it later will have any effect.
First, I apologize if my title isn't technically accurate. I'm not sure exactly what's happening and that describes it about as well as anything.
I am attempting to decode an SSL certificate for a specific use in a program. I p/invoked all of the necessary CryptoAPI functions and structs and in debug mode, everything is working as expected. However, when the program is run in release mode as a service (that's the purpose of this program), I get an access violation when attempting to decode the extensions. In order to make extensions easier to decode, I have created a generic class that can be used to represent any extension. This class contains an object of type TStruct that represents the underlying structure that the extension is based on. It has an EncryptedValue field of type byte[] that will encrypt/decrypt the extension data and place it in the structure as appropriate. The base class is as follows:
public abstract class ExtensionBase<TStruct> : IExtensionBase where TStruct : new()
{
//The underlying struct
protected TStruct objectData = new TStruct();
//The identifier of the struct, ie: szOID_BASIC_CONSTRAINTS
public string Identifier { get; protected set; }
//An enum representing the structure type, ie: X509_BASIC_CONSTRAINTS
public CertStructType StructureType { get; protected set; }
//Determines if the extension is critical
public bool IsCritical { get; protected set; }
//Overridden in any child class to determine if that extension actually contains
//data that should be encoded
public abstract bool HasData { get; }
//Encrypts/decrypts the data from/to the underlying structure
public virtual byte[] EncryptedValue
{
get
{
uint encodedSize = 0;
//PinnedHandle is a class that I wrote to wrap a GCHandle.
//It has an implicit cast to IntPtr that returns GCHandle.AddrOfPinnedObject
//The finalizer of the class releases the GCHandle if it is a valid handle
IntPtr dataPtr = new PinnedHandle(objectData);
byte[] retVal = null;
if (StructureType != CertStructType.None)
{
if (!Crypt32.CryptEncodeObjectEx((uint)CertEncoding.X509Asn,
(uint)StructureType,
dataPtr,
0,
IntPtr.Zero,
null,
ref encodedSize))
throw new Win32Exception();
retVal = new byte[encodedSize];
if (!Crypt32.CryptEncodeObjectEx((uint)CertEncoding.X509Asn,
(uint)StructureType,
dataPtr,
0,
IntPtr.Zero,
retVal,
ref encodedSize))
throw new Win32Exception();
}
else
{
if (!Crypt32.CryptEncodeObjectEx((uint)CertEncoding.X509Asn,
Identifier,
dataPtr,
0,
IntPtr.Zero,
null,
ref encodedSize))
throw new Win32Exception();
retVal = new byte[encodedSize];
if (!Crypt32.CryptEncodeObjectEx((uint)CertEncoding.X509Asn,
Identifier,
dataPtr,
0,
IntPtr.Zero,
retVal,
ref encodedSize))
throw new Win32Exception();
}
return retVal;
}
set
{
uint decodedSize = 0;
IntPtr decodedData = IntPtr.Zero;
if(StructureType != CertStructType.None)
decodedData = Crypt32.CryptDecodeObjectEx(StructureType, value);
else
decodedData = Crypt32.CryptDecodeObjectEx(Identifier, value);
TStruct data = (TStruct)Marshal.PtrToStructure(decodedData, typeof(TStruct));
objectData = data;
Marshal.FreeHGlobal(decodedData);
}
}
public ExtensionBase(string id)
{
Identifier = id;
StructureType = CertStructType.None;
}
public ExtensionBase(string id, CertStructType structType)
{
Identifier = id;
StructureType = structType;
}
}
One of the child classes that is giving me problems is the CertKeyUsage class which uses a CRYPT_BIT_BLOB struct to represent its data:
public class CertKeyUsage : ExtensionBase<CertKeyUsageFlags, CRYPT_BIT_BLOB>
{
public override bool HasData
{
get { return Value != CertKeyUsageFlags.None; }
}
public override unsafe byte[] EncryptedValue
{
get
{
CertKeyUsageFlags flags = Value;
objectData.cbData = 2;
objectData.cUnusedBits = 0;
objectData.pbData = new IntPtr(&flags);
return base.EncryptedValue;
}
set
{
try
{
//The following code was taken directly from Microsoft's implementation
//of X509Certificate
base.EncryptedValue = value;
if (objectData.cbData > 4)
objectData.cbData = 4;
byte[] keyUsage = new byte[4];
//This if statement returns true, and the following Marshal.Copy statement
//is where the program crashes with the Access Violation. As it is an unmanaged
//exception, the try/catch block doesn't do anything and the app dies
if (objectData.pbData != IntPtr.Zero)
Marshal.Copy(objectData.pbData, keyUsage, 0, (int) objectData.cbData);
Value = (CertKeyUsageFlags) BitConverter.ToUInt32(keyUsage, 0);
}
catch
{
}
}
}
public CertKeyUsage()
: base(CertOid.szOID_KEY_USAGE, CertStructType.X509KeyUsage)
{
IsCritical = true;
}
}
What about this code would be different from debug to release that would cause an access violation at the spot noted above? What could I do differently to get it working properly. This particular extension isn't critical and I could just skip over it, but after commenting out the code above that is causing the exception, another of the extensions will crash. I can't comment out all extensions, and the fact that it's moving to a different location tells me that there is some underlying problem with my code that I'm missing. Any help or suggestions would be greatly appreciated.
These are the p/invoked functions I'm calling:
[DllImport("crypt32.dll", CharSet = CharSet.Ansi, SetLastError = true)]
public static extern bool CryptEncodeObjectEx(uint certEncodingType,
[MarshalAs(UnmanagedType.LPStr)]
string structType,
IntPtr structInfo,
uint flags,
IntPtr encodePara,
byte[] encodedData,
[In, Out] ref uint encodedSize);
[DllImport("crypt32.dll", CharSet = CharSet.Ansi, SetLastError = true)]
public static extern bool CryptEncodeObjectEx(uint certEncodingType,
uint structType,
IntPtr structInfo,
uint flags,
IntPtr encodePara,
byte[] encodedData,
[In, Out] ref uint encodedSize);
[DllImport("crypt32.dll", CharSet = CharSet.Ansi, SetLastError = true)]
public static extern bool CryptDecodeObjectEx(uint certEncodingType,
[MarshalAs(UnmanagedType.LPStr)]
string structType,
byte[] encodedData,
uint encodedDataSize,
uint flags,
IntPtr encodePara,
IntPtr decodedData,
[In, Out] ref uint decodedDataSize);
[DllImport("crypt32.dll", CharSet = CharSet.Ansi, SetLastError = true)]
public static extern bool CryptDecodeObjectEx(uint certEncodingType,
uint structType,
byte[] encodedData,
uint encodedDataSize,
uint flags,
IntPtr encodePara,
IntPtr decodedData,
[In, Out] ref uint decodedDataSize);
And the function that wraps CryptDecodeObjectEx:
public static IntPtr CryptDecodeObjectEx(string structType, byte[] encodedData)
{
uint encodedSize = (uint)encodedData.Length;
uint decodedSize = 0;
IntPtr decodedData = IntPtr.Zero;
if (!CryptDecodeObjectEx((uint)CertEncoding.X509Asn,
structType,
encodedData,
encodedSize,
0,
IntPtr.Zero,
decodedData,
ref decodedSize))
throw new Win32Exception();
decodedData = Marshal.AllocHGlobal((int)decodedSize);
if (!CryptDecodeObjectEx((uint)CertEncoding.X509Asn,
structType,
encodedData,
encodedSize,
0,
IntPtr.Zero,
decodedData,
ref decodedSize))
throw new Win32Exception();
return decodedData;
}
public static IntPtr CryptDecodeObjectEx(uint structType, byte[] encodedData)
{
uint encodedSize = (uint)encodedData.Length;
uint decodedSize = 0;
IntPtr decodedData = IntPtr.Zero;
if (!CryptDecodeObjectEx((uint)CertEncoding.X509Asn,
structType,
encodedData,
encodedSize,
0,
IntPtr.Zero,
decodedData,
ref decodedSize))
throw new Win32Exception();
decodedData = Marshal.AllocHGlobal((int)decodedSize);
if (!CryptDecodeObjectEx((uint)CertEncoding.X509Asn,
structType,
encodedData,
encodedSize,
0,
IntPtr.Zero,
decodedData,
ref decodedSize))
throw new Win32Exception();
return decodedData;
}
On a whim, I decided to compile the program as X86 rather than AnyCPU and everything works as expected. This leads me to believe that the problem is actually in this section of code:
public static IntPtr Next<T>(this IntPtr val)
{
T retVal = (T)Marshal.PtrToStructure(val, typeof(T));
if (Environment.Is64BitProcess)
return (IntPtr)((long)val + Marshal.SizeOf(retVal));
return (IntPtr)((int)val + Marshal.SizeOf(retVal));
}
This is the code that I use to enumerate through a pointer to an array of structures. The structure type is passed in and an IntPtr to the next structure in the array should be returned. The 32 bit version is working just as it should, but apparently my logic in the 64 bit version is somewhat lacking.
My Silverlight app needs to access X509Store through native methods like this :
public class CapiNative
{
public const string MY = "MY";
public const uint PKCS_7_ASN_ENCODING = 0x00010000;
public const uint X509_ASN_ENCODING = 0x00000001;
public const uint CERT_FIND_SUBJECT_STR = 0x00080007;
public const int ACCESS_DENIED = 5;
[DllImport("crypt32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern IntPtr CertOpenSystemStore(
IntPtr hCryptProv,
string storename);
[DllImport("crypt32.dll", SetLastError = true)]
public static extern IntPtr CertFindCertificateInStore(
IntPtr hCertStore,
uint dwCertEncodingType,
uint dwFindFlags,
uint dwFindType,
[In, MarshalAs(UnmanagedType.LPWStr)]String pszFindString,
IntPtr pPrevCertCntxt);
internal static void CertEnumCertificatesInStore(IntPtr storeHandle, IntPtr certHandle)
{
throw new NotImplementedException();
}
}
public IntPtr FindCert(ref string err)
{
IntPtr storeHandle = CapiNative.CertOpenSystemStore(
IntPtr.Zero,
CapiNative.MY);
if (Marshal.GetLastWin32Error() == CapiNative.ACCESS_DENIED)
{
err = "Access Denied to the X509Store";
return IntPtr.Zero;
}
try
{
IntPtr certHandle = CapiNative.CertFindCertificateInStore(
storeHandle,
CapiNative.PKCS_7_ASN_ENCODING | CapiNative.X509_ASN_ENCODING,
0,
CapiNative.CERT_FIND_SUBJECT_STR,
_subject,
IntPtr.Zero);
X509Certificate foundcert = new X509Certificate(certHandle);
Console.WriteLine("\nFound certificate with SubjectName string \"{0}\"",_subject);
Console.WriteLine("SubjectName:\t{0}", foundcert.Issuer);
Console.WriteLine("Serial No:\t{0}", foundcert.GetSerialNumberString());
Console.WriteLine("HashString:\t{0}" , foundcert.GetCertHashString());
return certHandle;
}
catch (Exception e)
{
err = "Error getting certificate " + e.Message;
return IntPtr.Zero;
}
}
When I'm using the constructor X509Certificate(IntPtr) I get a MethodAccessException, I guess I can't use this method in Silverlight.
I also tried to use this technique :
https://stackoverflow.com/a/17340419/969881
Like this :
public X509Certificate IntPtrToObject(IntPtr ptrToUnwrap, ref string err)
{
if (ptrToUnwrap == IntPtr.Zero)
{
return null;
}
try
{
X509Certificate x509Cert = new X509Certificate();
System.Runtime.InteropServices.Marshal.PtrToStructure(ptrToUnwrap, x509Cert);
return x509;
}
catch (Exception e)
{
err = e.Message;
return null;
}
}
But I get the following error message :
Object contains non-primitive or non-blittable data. The structure
must be specified blittable or have information provided. Parameter
name: structure
Is it possible to parse a X509Certificate with the P/Invoke methods ?
Unfortunately, I'm not a Silverlight expert, and don't know what functionality is stripped there, however you can use workaround:
1) define CERT_CONTEXT structure as follows
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
public struct CERT_CONTEXT {
public Int32 dwCertEncodingType;
public IntPtr pbCertEncoded;
public Int32 cbCertEncoded;
public IntPtr pCertInfo;
public IntPtr hCertStore;
}
and when acquire cert handle from CertFindCertificateInStore function (ensure if this is not equals to IntPtr.zero.
2) Then use Marshal.PtrToStructure method to copy pointer to a structure:
CapiNative.CERT_CONTEXT certcontext = (CapiNative.CERT_CONTEXT)Marshal.PtrToStructure(certHandle, typeof(CapiNative.CERT_CONTEXT));
3) then you can transfortm pCertInfo member of CERT_CONTEXT structure to CERT_INFO structure, but I would try to grab raw bytes from this structure:
byte[] rawData = new byte[certContext.cbCertEncoded];
Marshal.Copy(certContext.pbCertEncoded, rawData, 0, rawData.Length);
X509Certificate2 cert = new X509Certificate2(rawData);
BTW, do not forget to close certificate store after completing the certificate access to avoid memory leaks. Store is closed by using a CertCloseStore function.
BTW2, the code part from your original posting:
X509Certificate x509Cert = new X509Certificate();
System.Runtime.InteropServices.Marshal.PtrToStructure(ptrToUnwrap, x509Cert);
return x509;
is incorrect, because PtrToStructure expects a C-style structure with layout information, and its members must be of blittable types. You can't pass managed classes/structures to unmanaged code/memory.
Is there any specific reason why not to use X509Store class in .NET?
I'm working on a WPF app which allows user to drag and drop files from Windows Explorer. For normal files, I'm able to access the path using
string[] fileNames = (string[])e.Data.GetData(DataFormats.FileDrop, false);
But for WPD files, its returning null. Have tried to follow the solution given in http://us.generation-nt.com/answer/drag-drop-pictures-wpd-camera-help-31497882.html#r , but i couldn't make it work. I'm getting AccessVoilationException when trying to get the count of items from the Shell array. I have posted the question in MSDN(http://social.msdn.microsoft.com/Forums/vstudio/en-US/ef7fc152-dd1b-4774-adb7-47b48726daea/drag-drop-from-windows-portable-device-to-wpf-application?forum=wpf), but didn't get any leads.
Is there something that I'm missing here? Could you please help me solve this issue?
Following is the relevant part my code.
public enum SIGDN : uint
{
NORMALDISPLAY = 0,
PARENTRELATIVEPARSING = 0x80018001,
PARENTRELATIVEFORADDRESSBAR = 0x8001c001,
DESKTOPABSOLUTEPARSING = 0x80028000,
PARENTRELATIVEEDITING = 0x80031001,
DESKTOPABSOLUTEEDITING = 0x8004c000,
FILESYSPATH = 0x80058000,
URL = 0x80068000
}
internal class IIDGuid
{
private IIDGuid() { } // Avoid FxCop violation AvoidUninstantiatedInternalClasses
// IID GUID strings for relevant COM interfaces
internal const string IModalWindow = "b4db1657-70d7-485e-8e3e-6fcb5a5c1802";
internal const string IFileDialog = "42f85136-db7e-439c-85f1-e4075d135fc8";
internal const string IFileOpenDialog = "d57c7288-d4ad-4768-be02-9d969532d960";
internal const string IFileSaveDialog = "84bccd23-5fde-4cdb-aea4-af64b83d78ab";
internal const string IFileDialogEvents = "973510DB-7D7F-452B-8975-74A85828D354";
internal const string IShellItem = "43826D1E-E718-42EE-BC55-A1E261C37BFE";
internal const string IShellItemArray = "B63EA76D-1F85-456F-A19C-48159EFA858B";
}
[ComImport]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[Guid("43826d1e-e718-42ee-bc55-a1e261c37bfe")]
public interface IShellItem
{
void BindToHandler(IntPtr pbc,
[MarshalAs(UnmanagedType.LPStruct)]Guid bhid,
[MarshalAs(UnmanagedType.LPStruct)]Guid riid,
out IntPtr ppv);
void GetParent(out IShellItem ppsi);
void GetDisplayName(SIGDN sigdnName, out IntPtr ppszName);
void GetAttributes(uint sfgaoMask, out uint psfgaoAttribs);
void Compare(IShellItem psi, uint hint, out int piOrder);
};
[ComImport]
[Guid(IIDGuid.IShellItemArray)]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
internal interface IShellItemArray
{
// Not supported: IBindCtx
void BindToHandler([In, MarshalAs(UnmanagedType.Interface)] IntPtr pbc, [In] ref Guid rbhid, [In] ref Guid riid, out IntPtr ppvOut);
void GetPropertyStore([In] int Flags, [In] ref Guid riid, out IntPtr ppv);
void GetCount(out uint pdwNumItems);
void GetItemAt([In] uint dwIndex, [MarshalAs(UnmanagedType.Interface)] out IShellItem ppsi);
void EnumItems([MarshalAs(UnmanagedType.Interface)] out IntPtr ppenumShellItems);
}
[DllImport("shell32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
static extern int SHCreateShellItemArrayFromDataObject(
System.Runtime.InteropServices.ComTypes.IDataObject pdo,
ref Guid riid,
[MarshalAs(UnmanagedType.Interface)] out IShellItemArray ppv);
[DllImport("kernel32.dll", SetLastError = true)]
static extern Int32 GetLastError();
private void OnFileDrop(object sender, DragEventArgs e)
{
string[] fileNames = (string[])e.Data.GetData(DataFormats.FileDrop, false);// null
System.Runtime.InteropServices.ComTypes.IDataObject mydata = e.Data as System.Runtime.InteropServices.ComTypes.IDataObject;
IShellItemArray nativeShellItemArray;
Guid guid = new Guid(IIDGuid.IShellItemArray);
int retCode = SHCreateShellItemArrayFromDataObject(mydata, ref guid, out nativeShellItemArray);
IShellItem nativeShellItem;
if (retCode == 0)
{
IntPtr displayname;
uint items = 0;
try
{
nativeShellItemArray.GetCount(out items); //Getting AccessVoilationException in this line
}
catch (Exception ex)
{
}
if (items > 0)
{
for (uint item = 0; item < items; item++)
{
nativeShellItemArray.GetItemAt(item, out nativeShellItem);
nativeShellItem.GetDisplayName(SIGDN.DESKTOPABSOLUTEPARSING, out displayname);
//Do something
}
}
}
}
I have a problem I'm trying to get a window's title by it's process name and I can't do it here s what I tried :
Process[] p = Process.GetProcessesByName("Acrobat");
Console.WriteLine(p[0].MainWindowTitle);
Console.ReadLine();
But the problem is that I can only get it only if the associated process does have a main window. How can I make it working ?
The main goal is that I've a method named BringToFront()
But this method ask for a caption name which is "thenameofthePDF.pdf - Adobe Acrobat Pro (Yes, acrobat is running with an opened pdf)
I would like to bring to front my Acrobat window..
but for this I need the name of the windows as my method is asking for the caption.
Here is the entire code at the moment:
class Program
{
[DllImport("User32.dll")]
public static extern Int32 SetForegroundWindow(int hWnd);
[DllImport("user32.dll")]
public static extern int FindWindow(string lpClassName, string lpWindowName);
private static void BringToFront(string className, string CaptionName)
{
SetForegroundWindow(FindWindow(className, CaptionName));
}
static void Main(string[] args)
{
// BringToFront("Acrobat", "mypdf.pdf - Adobe Acrobate Pro");
Process[] p = Process.GetProcesses();
foreach (var process in p)
{
Console.WriteLine(process.MainWindowTitle);
}
Console.ReadLine();
}
}
Did you read the manual, does the following apply?
A process has a main window associated with it only if the process has a graphical interface. If the associated process does not have a main window (so that MainWindowHandle is zero), MainWindowTitle is an empty string (""). If you have just started a process and want to use its main window title, consider using the WaitForInputIdle method to allow the process to finish starting, ensuring that the main window handle has been created. Otherwise, the system throws an exception.
One possible solution is to enumerate all top-level windows and pick the one you are interested in. In your case this would be all windows with a class of AcrobatSDIWindow and a window title starting with your document name.
class Program
{
public class SearchData
{
public string ClassName { get; set; }
public string Title { get; set; }
private readonly List<IntPtr> _result = new List<IntPtr>();
public List<IntPtr> Result
{
get { return _result; }
}
}
[DllImport("User32.dll")]
public static extern Int32 SetForegroundWindow(IntPtr hWnd);
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool EnumWindows(EnumWindowsProc lpEnumFunc, ref SearchData data);
private delegate bool EnumWindowsProc(IntPtr hWnd, ref SearchData data);
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
public static extern int GetClassName(IntPtr hWnd, StringBuilder lpClassName, int nMaxCount);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern int GetWindowText(IntPtr hWnd, StringBuilder lpString, int nMaxCount);
public static bool EnumProc(IntPtr hWnd, ref SearchData searchData)
{
var sbClassName = new StringBuilder(1024);
GetClassName(hWnd, sbClassName, sbClassName.Capacity);
if (searchData.ClassName == null || Regex.IsMatch(sbClassName.ToString(), searchData.ClassName))
{
var sbWindowText = new StringBuilder(1024);
GetWindowText(hWnd, sbWindowText, sbWindowText.Capacity);
if (searchData.Title == null || Regex.IsMatch(sbWindowText.ToString(), searchData.Title))
{
searchData.Result.Add(hWnd);
}
}
return true;
}
static void Main(string[] args)
{
var searchData = new SearchData
{
ClassName = "AcrobatSDIWindow",
Title = "^My Document\\.pdf.*"
};
EnumWindows(EnumProc, ref searchData);
var firstResult = searchData.Result.FirstOrDefault();
if (firstResult != IntPtr.Zero)
{
SetForegroundWindow(firstResult);
}
}
}
If you dump them all out using GetProcesses() it appears that the window title will have 'Adobe Reader', prefixed by the name of the PDF if one is open. So you might need to do that and walk the array instead.
For example if I have UserManual.pdf open and I run the code below it displays UserManual.pdf - Adobe Reader on the console:
Process[] p = Process.GetProcesses();
String Title = String.Empty;
for (var i = 0; i < p.Length; i++)
{
Title = p[i].MainWindowTitle;
if (Title.Contains(#"Adobe"))
Console.WriteLine(Title);
}