Why does CreateProcess ignore STARTUPINFO window size values? - c#

I am trying to use CreateProcess to start Notepad at a specific window size to avoid the flashing that occurs when it appears somewhere for a split second before I can resize it and move it.
CreateProcess starts Notepad but CreateProcess and Notepad both ignore the window dimensions in the STARTUPINFO structure. Notepad appears in the normal place at the normal size where it was last closed.
No combination of dwFlags that I tried worked. Notepad either does not appear at all or ignores my settings and appears in a place and size determined by the operating system.
Why is CreateProcess ignoring the values I set in STARTUPINFO? Am I missing something?
[TestMethod()]
public void CreateProcessTest() {
const uint NORMAL_PRIORITY_CLASS = 0x0020;
const uint STARTF_USESHOWWINDOW = 0x0001;
// create structures needed by CreateProcess
var pInfo = new Kernel32.PROCESS_INFORMATION();
var pSec = new Kernel32.SECURITY_ATTRIBUTES();
var tSec = new Kernel32.SECURITY_ATTRIBUTES();
pSec.nLength = Marshal.SizeOf(pSec);
tSec.nLength = Marshal.SizeOf(tSec);
// set the app and a file to open
var app = Environment.GetEnvironmentVariable("windir") + #"\notepad.exe";
var arguments = #" C:\somefile.txt";
// the started app window does not use these values
var sInfo = new Kernel32.STARTUPINFO();
sInfo.dwX = 800; // desired x-y position of the window
sInfo.dwY = 400;
sInfo.dwXSize = 200; // desired size of the window
sInfo.dwYSize = 400;
// no combination of these flags that I tried makes any difference
// Notepad always appears as normal and ignores the size settings above
sInfo.dwFlags = STARTF_USESHOWWINDOW;
sInfo.wShowWindow = (short) Win32.SW_SHOW;
// create the process
var result = Kernel32.CreateProcess(app, arguments,
ref pSec, ref tSec, false, NORMAL_PRIORITY_CLASS,
IntPtr.Zero, null, ref sInfo, out pInfo);
}

Your entire STARTUPINFO structure is being ignored, since you did not set the cb member correctly. I am a bit surprised that CreateProcess is not failing outright with ERROR_INVALID_PARAMETER.

According to the MSDN:
the dwFlags member of STARTUPINFO needs to add STARTF_USEPOSITION and STARTF_USESIZE.
Edited,
Store in registry directory:Computer\HKEY_CURRENT_USER\SOFTWARE\Microsoft\Notepad.
iWindowPosDX
iWindowPosDY
iWindowPosX
iWindowPosY

Related

C# get default application for file type

I'm using this code to get the default application for file types like ".txt".
It works completely fine but why do I have to call the same method two times? The only thing I found out is, that the lenth is set after the first call. But do I really have to execute the function twice because I need the length first?
If I execute it only once, it crashes.
CODE
using System.Runtime.InteropServices;
[DllImport("Shlwapi.dll", CharSet = CharSet.Unicode)]
public static extern uint AssocQueryString(
AssocF flags,
AssocStr str,
string pszAssoc,
string pszExtra,
[Out] StringBuilder pszOut,
ref uint pcchOut
);
[Flags]
public enum AssocF
{
None = 0,
Init_NoRemapCLSID = 0x1,
Init_ByExeName = 0x2,
Open_ByExeName = 0x2,
Init_DefaultToStar = 0x4,
Init_DefaultToFolder = 0x8,
NoUserSettings = 0x10,
NoTruncate = 0x20,
Verify = 0x40,
RemapRunDll = 0x80,
NoFixUps = 0x100,
IgnoreBaseClass = 0x200,
Init_IgnoreUnknown = 0x400,
Init_Fixed_ProgId = 0x800,
Is_Protocol = 0x1000,
Init_For_File = 0x2000
}
public enum AssocStr
{
Command = 1,
Executable,
FriendlyDocName,
FriendlyAppName,
NoOpen,
ShellNewValue,
DDECommand,
DDEIfExec,
DDEApplication,
DDETopic,
InfoTip,
QuickTip,
TileInfo,
ContentType,
DefaultIcon,
ShellExtension,
DropTarget,
DelegateExecute,
Supported_Uri_Protocols,
ProgID,
AppID,
AppPublisher,
AppIconReference,
Max
}
SAMPLE USAGE:
static string AssocQueryString(AssocStr association, string extension)
{
const int S_OK = 0;
const int S_FALSE = 1;
uint length = 0;
uint ret = AssocQueryString(AssocF.None, association, extension, null, null, ref length);
if (ret != S_FALSE)
{
throw new InvalidOperationException("Could not determine associated string");
}
var sb = new StringBuilder((int)length); // (length-1) will probably work too as the marshaller adds null termination
ret = AssocQueryString(AssocF.None, association, extension, null, sb, ref length);
if (ret != S_OK)
{
throw new InvalidOperationException("Could not determine associated string");
}
return sb.ToString();
}
AssocQueryString is a WinAPI function. This is a kind of system-level general-purpose function, which might be used in different kinds of applications. Using applications might happen to have VERY tight performance and/or memory requirements. That's why WinAPI functions never do memory allocations themselves (as memory allocation might be a relatively expensive task performance-wise), they expect all the required memory to be provided to them by the caller.
In many cases (like with this AssocQueryString functionality) one can not know the required amount of memory before the function execution. Here the API developers "merged" two functions into one: if you call AssocQueryString with null instead of output string it will calculate you the required string length, otherwise it will use the string you've provided expecting that you've already allocated enough memory for that string.
You don't need to worry about calling the function twice. In fact, you're calling two slightly different functions: one is to calculate the required string length and another is to actually do the job (i.e. search the registry for a file association).

C# console application stdin/stdout redirection

I have an interesting (read: frustrating) problem kicking off a console application from a C# WPF app and redirecting its stdin and stdout.
It is mostly up and working but I seem to end up not getting some data from stdout as soon as I start redirecting stdin.
I'll clarify with an example. If I don't set hStdInput in the STARTUPINFO structure, when I start the child process I receive the following:
MongoDB shell version: 2.2.0
connecting to: test
local:PRIMARY>
Once I set hStdInput however, I just get this:
MongoDB shell version: 2.2.0
connecting to: test
I know that the BackgroundWorker processing stdout is still functioning because if I send something to the process on stdin, it responds accordingly.
use TestDB
switched to db TestDB
So, this is how I create the process:
_processInfo = new ProcessInfo();
bool ok = false;
SECURITY_ATTRIBUTES sattr = new SECURITY_ATTRIBUTES();
sattr.bInheritHandle = 1;
unsafe
{
sattr.lpSecurityDescriptor = null;
}
sattr.nLength = Marshal.SizeOf(sattr);
IntPtr hWrite;
ok = CreatePipe(out _hReadStdOut, out hWrite, ref sattr, 0);
ok = SetHandleInformation(_hReadStdOut, HANDLE_FLAGS.INHERIT, 0);
IntPtr hRead;
ok = CreatePipe(out hRead, out _hWriteStdIn, ref sattr, 0);
ok = SetHandleInformation(_hWriteStdIn, HANDLE_FLAGS.INHERIT, 0);
var startInfo = new StartupInfo
{
dwFlags = 0x0001 | 0x0100,
wShowWindow = 0,
hStdOutput = hWrite,
hStdError = hWrite,
hStdInput = hRead // If this is IntPtr.Zero, I get everything from stdout
};
SECURITY_ATTRIBUTES pSec = new SECURITY_ATTRIBUTES();
pSec.nLength = Marshal.SizeOf(pSec);
SECURITY_ATTRIBUTES tSec = new SECURITY_ATTRIBUTES();
tSec.nLength = Marshal.SizeOf(tSec);
unsafe
{
ok = CreateProcess(
null,
pathToExeAndArgs,
ref pSec,
ref tSec,
true,
0,
IntPtr.Zero,
null,
ref startInfo,
out _processInfo);
}
I have a BackgroundWorker processing stdout on DoWork which reads the pipe like so:
success = ReadFile(
_hReadStdOut,
bufPtr,
1024,
&read,
IntPtr.Zero);
I'm not using the .Net Process class because it didn't obtain data from stdout until the console application sent a newline, so I didn't get the prompt back in that case either.
Any help with this greatly appreciated.
Cheers.
I suspect the following explains what you have observed:
When you don't define hStdInput the child process uses the standard input device attached to the console. The child process detects that standard input is an interactive console device and writes a prompt.
When you do define hStdInput the child process detects that the standard input is a pipe and so neglects to write a prompt. After all, what's the point of prompting a non-interactive input device?
The child process will use GetFileType(GetStdHandle(STD_INPUT_HANDLE)) to detect what type of device is attached to the standard input. A value of FILE_TYPE_CHAR indicates a console. When you attach a pipe to the standard input then the standard input file type will be FILE_TYPE_PIPE.
My conclusion is that everything is working as designed and intended.

How do I determine the icon index for Desktop and Network for use in SHGetImageList?

I am able to successfully extract the icons for file system drives, folders and files using the APIs I included below. Additional info on the DLL imports etc. that helped me get this far can be found here. By calling the method GetExtraLargeIconForFolder I get a 48x48 sized image in the icon.
public enum ImageListIconSize : int
{
Large = 0x0,
Small = 0x1,
ExtraLarge = 0x2,
Jumbo = 0x4
}
private static IImageList GetSystemImageListHandle(ImageListIconSize size)
{
IImageList iImageList;
Guid imageListGuid = new Guid("46EB5926-582E-4017-9FDF-E8998DAA0950");
int ret = SHGetImageList(
(int)size,
ref imageListGuid,
out iImageList
);
return iImageList;
}
public static Icon GetExtraLargeIconForFolder(string path)
{
SHFILEINFO shinfo = new SHFILEINFO();
IntPtr retVal = SHGetFileInfo(
path, 0, ref shinfo, (uint)Marshal.SizeOf(shinfo),
(int)(SHGetFileInfoConstants.SHGFI_SYSICONINDEX |
SHGetFileInfoConstants.SHGFI_ICON));
int iconIndex = shinfo.iIcon;
IImageList iImageList =
(IImageList)GetSystemImageListHandle(ImageListIconSize.ExtraLarge);
IntPtr hIcon = IntPtr.Zero;
if (iImageList != null)
{
iImageList.GetIcon(iconIndex,
(int)ImageListDrawItemConstants.ILD_TRANSPARENT, ref hIcon);
}
Icon icon = null;
if (hIcon != IntPtr.Zero)
{
icon = Icon.FromHandle(hIcon).Clone() as Icon;
DestroyIcon(shinfo.hIcon);
}
return icon;
}
In Windows Explorer one sees, icons for the Desktop, Network and Computer. How does one go about getting the correct icon index for these file system nodes?
You are nearly there. You still use SHGetFileInfo but instead you will need to pass SHGFI_PIDL in the flags parameter.
Then you need to specify the shell object of interest by passing a PIDL rather than a path. Obtain the PIDL by calling SHGetSpecialFolderLocation. Pass a CSIDL value to this routine, e.g. CSIDL_DESKTOP, CSIDL_DRIVES, CSIDL_NETWORK etc.

C# STARTUPINFO Flags to show UI for a process started from a Service in XP

I'm launching a process from a windows service in XP, I'm just launching the process not trying to interact with it. The process starts but the UI does not show. I believe I need to set some flags in STARTUPINFO to make process visible, and hoping someone could show how and what flags to set.
sPath = #"C:\Windows\notepad.exe";
string Message = string.Empty;
// Variables
PROCESS_INFORMATION processInfo = new PROCESS_INFORMATION();
STARTUPINFO startInfo = new STARTUPINFO();
Boolean bResult = false;
IntPtr hToken = IntPtr.Zero;
try
{
// Logon user
bResult = LogonUser(
"Test",
"VirtualXP-23639",
"test",
LogonType.LOGON32_LOGON_INTERACTIVE,
LogonProvider.LOGON32_PROVIDER_DEFAULT,
out hToken
);
if (!bResult) { throw new Exception("Logon error #" + Marshal.GetLastWin32Error()); }
// Create process
startInfo.cb = Marshal.SizeOf(startInfo);
startInfo.lpDesktop = "winsta0\\default";
bResult = CreateProcessAsUser(
hToken,
null,
sPath,
IntPtr.Zero,
IntPtr.Zero,
false,
0,
IntPtr.Zero,
null,
ref startInfo,
out processInfo
);
if (!bResult)
{
Message = "Failed to Create Process on Desktop/Console. Code=" + Marshal.GetLastWin32Error().ToString();
Logging.LogError(Ascension.CM.Common.Enums.ApplicationModuleEnums.Service, Message, "Ascension.CM.ServiceWorker.ProcessLauncher.XpLaunchDesktopProcess", null);
}
}
finally
{
// Close all handles
CloseHandle(hToken);
CloseHandle(processInfo.hProcess);
CloseHandle(processInfo.hThread);
}
}
You'll at least need to allow the service to interact with the desktop, so in services.msc, click on your serivce an go to properties, then logon and select allow to interact with desktop..
I would suggest that you use the Process class in the .net framework.
Process.Start("notepad.exe")
This should have your desired effect.
Thanks guys, but I've found a solution.
I ended up using WTSQueryUserToken to get the current logged in user and then used DuplicateTokenEx to get a token that I used with CreateProcessAsUser to start the process.
For XP use session id 0 and for win7 use WTSGetActiveConsoleSessionId to get the current session Id.
This works fine with out having to use the "Allow to interact with Desktop" property.
Thanks

Finding the default application for opening a particular file type on Windows

I'm developing an application targeting .NET Framework 2.0 using C# for which I need to be able to find the default application that is used for opening a particular file type.
I know that, for example, if you just want to open a file using that application you can use something like:
System.Diagnostics.Process.Start( "C:\...\...\myfile.html" );
to open an HTML document in the default browser, or
System.Diagnostics.Process.Start( "C:\...\...\myfile.txt" );
to open a text file in the default text editor.
However, what I want to be able to do is to open files that don't necessarily have a .txt extension (for example), in the default text editor, so I need to be able to find out the default application for opening .txt files, which will allow me to invoke it directly.
I'm guessing there's some Win32 API that I'll need to P/Invoke in order to do this, however a quick look with both Google and MSDN didn't reveal anything of much interest; I did find a very large number of completely irrelevant pages, but nothing like I'm looking for.
All current answers are unreliable. The registry is an implementation detail and indeed such code is broken on my Windows 8.1 machine. The proper way to do this is using the Win32 API, specifically AssocQueryString:
using System.Runtime.InteropServices;
[DllImport("Shlwapi.dll", CharSet = CharSet.Unicode)]
public static extern uint AssocQueryString(
AssocF flags,
AssocStr str,
string pszAssoc,
string pszExtra,
[Out] StringBuilder pszOut,
ref uint pcchOut
);
[Flags]
public enum AssocF
{
None = 0,
Init_NoRemapCLSID = 0x1,
Init_ByExeName = 0x2,
Open_ByExeName = 0x2,
Init_DefaultToStar = 0x4,
Init_DefaultToFolder = 0x8,
NoUserSettings = 0x10,
NoTruncate = 0x20,
Verify = 0x40,
RemapRunDll = 0x80,
NoFixUps = 0x100,
IgnoreBaseClass = 0x200,
Init_IgnoreUnknown = 0x400,
Init_Fixed_ProgId = 0x800,
Is_Protocol = 0x1000,
Init_For_File = 0x2000
}
public enum AssocStr
{
Command = 1,
Executable,
FriendlyDocName,
FriendlyAppName,
NoOpen,
ShellNewValue,
DDECommand,
DDEIfExec,
DDEApplication,
DDETopic,
InfoTip,
QuickTip,
TileInfo,
ContentType,
DefaultIcon,
ShellExtension,
DropTarget,
DelegateExecute,
Supported_Uri_Protocols,
ProgID,
AppID,
AppPublisher,
AppIconReference,
Max
}
Relevant documentation:
AssocQueryString
ASSOCF
ASSOCSTR
Sample usage:
static string AssocQueryString(AssocStr association, string extension)
{
const int S_OK = 0;
const int S_FALSE = 1;
uint length = 0;
uint ret = AssocQueryString(AssocF.None, association, extension, null, null, ref length);
if (ret != S_FALSE)
{
throw new InvalidOperationException("Could not determine associated string");
}
var sb = new StringBuilder((int)length); // (length-1) will probably work too as the marshaller adds null termination
ret = AssocQueryString(AssocF.None, association, extension, null, sb, ref length);
if (ret != S_OK)
{
throw new InvalidOperationException("Could not determine associated string");
}
return sb.ToString();
}
You can check under registry section HKEY_CLASSES_ROOT for the extension and action details. Documentation for this is on MSDN. Alternatively, you can use the IQueryAssociations interface.
Doh! Of course.
HKEY_CLASSES_ROOT\.txt
includes a reference to
HKEY_CLASSES_ROOT\txtfile
which contains a subkey
HKEY_CLASSES_ROOT\txtfile\shell\open\command
which references Notepad.
Sorted, many thanks!
Bart
Here is a blog post with about this topic. The code samples are in VB.net, but it should be easy to port them to C#.
You can just query the registry. First get the Default entry under HKEY_CLASSES_ROOT\.ext
That will give you the classname. For example .txt has a default of txtfile
Then open up HKEY_CLASSES_ROOT\txtfile\Shell\Open\Command
That will give you the default command used.
A late answer, but there is a good NUGET package that handles file associations: File Association
Link NUGET File Association
Usage is simple, for instance to add all allowed file extensions to a context menu:
private void OnMenuSourceFileOpening(object sender, ...)
{ // open a context menu with the associated files + ".txt" files
if (File.Exists(this.SelectedFileName))
{
string fileExt = Path.GetExtension(this.SelectedFileNames);
string[] allowedExtensions = new string[] { fileExt, ".txt" };
var fileAssociations = allowedExtensions
.Select(ext => new FileAssociationInfo(ext));
var progInfos = fileAssociations
.Select(fileAssoc => new ProgramAssociationInfo (fileAssoc.ProgID));
var toolstripItems = myProgInfos
.Select(proginfo => new ToolStripLabel (proginfo.Description) { Tag = proginfo });
// add also the prog info as Tag, for easy access
// when the toolstrip item is selected
// of course this can also be done in one long linq statement
// fill the context menu:
this.contextMenu1.Items.Clear();
this.contextMenuOpenSourceFile.Items.AddRange (toolstripItems.ToArray());
}
}

Categories