How to get around the command line length limit? - c#

I've been working on a small and simple program that I drop files onto and, based on certian rules, they are moved to diffrent places.
The program works fine unless I drop more than a few files, then it kicks back an error (that appears to be more Windows than anything) that the start up command "c:\myapp.exe \file \file \file" is too long.
I realize I could set up a background proccess, but I really would prefer that this program not run in the backround (where it would be idle most of the time).
Is there a way around this limitation?

From this blog:
The maximum command line length for the CreateProcess function is 32767 characters. This limitation comes from the UNICODE_STRING structure.
If you are using the CMD.EXE command processor, then you are also subject to the 8192 character command line length limit imposed by CMD.EXE.
If you are using the ShellExecute/Ex function, then you become subject to the INTERNET_MAX_URL_LENGTH (around 2048) command line length limit imposed by the ShellExecute/Ex functions.
The maximum size of your environment is 32767 characters. The size of the environment includes all the variable names plus all the values.
So you're gonna have to settle with some of the mentioned workarounds (also, there's another workaround on the msdn blog I linked).

If you want to drop files with respect of Windows Explorer, then you can implement your own Drop Handlers as a Shell Extension Handlers see:
How to Create Drop Handlers (Windows)
Creating Shell Extension Handlers
On The Complete Idiot's Guide to Writing Shell Extensions you will find a good introduction how to write such extensions.
The part VI gives an example of Drop Handler (for a little other use case, but it dose not matter).
With respect of Drop Shell Extension Handler your program will receive full information about all dropped files and you need not start a child program with all the files as command like parameters.

I think the drag and drop handler is possibly one way to go, but it seems quite heavy.
An alternative solution is to use a Explorer Context Menu handler. With this in place, you would select all the files, but rather than dragging them, right click, and choose your new menu item "Send to ".
When the menu item is selected, it passes the list of commands to your program. There are a couple of ways of doing this:
launch your program, and feed the list of files to standard input
write the list of files to a temporary file, and launch your program with just one command argument - the temporary file listing the files to process. List files are usually prefixed with '#' on the command line to distinguish them from ordinary file names.

When the files are drag&dropped onto your application write the list of files names out to a text file, then give the location of this file to your program. The target program can then read in this file and process it line-by-line. This way you only ever pass a single file name.

I'd modify the application to pick up the files from a specific location (e.g. specific folder on a filesystem) rather than specifying each file on the command line.
UPDATE:
If the requirement is to be able to drag an item onto an .exe via Windows Explorer to start the application as Mark mentioned then you could put all of your files into one folder and drop the entire folder on the .exe.

I think the easiest solution is to modify your application to accept a directory as the parameter as opposed to a list of files. This would allow you to copy the multiple files into a single directory. You can then drag and drop the folder onto your executable. It would then run a command like "c:\myapp.exe \folder-with-files-in-it" which should not run into the command line parameter limitation that you are experiencing now.

I experienced a similar issue when I was trying to get security details on a long path.
My solution was to map drives whenever the path length got too long. Check out my solution on
How do I get the security details for a long path?

Unix commands frequently have one or two parameters that can be of unbounded length. Several of these commands have added parameters which can source those arguments from a file, or from standard input. So you have one command which gins up the list of arguments, and either pipes them to a temporary file, or pipes them to stdout.
See also, xargs, which can take a list of arguments and either invoke your command with all of the parameters, or in batches.

How to get around the command line length limit? Write your whole command to a batch file, e.g. to "C:\Users\Johnny\Documents\mybatch.bat". Write it as you would in the cmd (no need to escape anything). Then, in your code simply call that file:
strCmdText = "C:\\Users\\Johnny\\Documents\\mybatch.bat";
ProcessStartInfo ProcessInfo = new ProcessStartInfo("cmd.exe", "/K " + strCmdText);
ProcessInfo.CreateNoWindow = true;
ProcessInfo.UseShellExecute = true;
Process.Start(ProcessInfo);

Related

Run an External Program or Batch File From a C# Xamarin Program

I want to have my C# (Xamarin) program run an EXE or batch (BAT) file. The user will be running my program, and will click on one of several buttons, some of which open Web pages and others of which run external programs. These files will be on the same computer as the one running the main program and don't need greater permissions. The overall program will be in Windows, UWP.
I already have code to pull info from the database saying "the button the user clicked references a program and it's (eg) C:\Tools\MyTool.exe". (Real path more like (C:\Users\Me\source\repos\ProductNameV2\ProductName\ProductName.UWP\Assets\EXE\whatever.exe".) I used a "demo.bat" file containing nothing but echo and pause statements, or references to a built-in Windows program like Notepad or Calc that an ordinary command prompt can recognize without an explicit path (ie. that's part of the recognized system Path). Yes, the real path to the dummy file does exist; I checked. I've also explicitly added files demo.bat and dummy.txt to my C# project.
Here's roughly what I've tried so far to actually run a batch file, or an EXE, or just to try opening a text file. Nothing works.
1)
bool check = await Launcher.CanOpenAsync(#"file:///C:\Tools\demo.bat"); // Returns false.
bool check = await Launcher.CanOpenAsync(#"file:///C:\Tools\dummy.txt"); // Returns true.
await Launcher.OpenAsync(#"file:///C:\Tools\demo.bat") // Seems to do nothing; silently fails.
await Launcher.OpenAsync(#"file:///C:\Tools\dummy.txt") // Same.
2)
Process batchProcess = new Process();
batchProcess.StartInfo.FileName = #"file:///C:\Tools\demo.bat"; // Same result with notepad.exe
batchProcess.StartInfo.WindowStyle = ProcessWindowStyle.Normal;
batchProcess.Start();
batchProcess.WaitForExit();
// Result: "Access is denied" error during Start().
3)
var otherProcessInfo = new ProcessStartInfo(#"file:///C:\Tools\demo.bat")
var otherProcess = Process.Start(otherProcessInfo);
otherProcess.WaitForExit();
otherProcess.Close();
// Result: "The system cannot find the file specified" despite it being the same path as in previous examples.
// Also tried literally using the path C:\Tools\demo.bat, without adding that to the C# project.
// One thing that slightly works is to use:
var otherProcessInfo = new ProcessStartInfo("cmd.exe", "/c echo Hello world!");
// This version opens a window and instantly closes it again. With "/c pause" instead, it opens, saying "press any key to continue".
// Chaining multiple commands with newline or semicolon characters doesn't work as a form of batch file.
So: the only tiny success I've had here is to run cmd.exe, to run a one-line command. I suppose that depending on what the batch file must do, there's some possibility of receiving a string, breaking it into lines, then running cmd.exe using method 3 to call them one at a time. Which is ugly at best.
Is there some better way to do this -- to run a batch file or an EXE from within my program?
EDIT: Yes, I did in fact look at documentation before asking. Why did I use URIs? Because of multiple errors telling me that the simple path strings ("C:\this\that") I was using were in an "Invalid URI format". Using Process.Start("notepad.exe") silently fails, doing nothing. Using a method involving System.Diagnostics.Process (found at How to run external program via a C# program? and yes I saw that before) fails with an error of "Access denied" when using my batch file reference, or silently failing (no window opens) using plain old notepad.exe. I avoided setting Process options that say hide the window.
So to rephrase: Is there a way to make my program run some EXE somewhere on the computer, or to run a batch file that has more than one command in it? What is that way?
Using the data you collected, I was able to run a batch file by doing the following:
var strPathToExeOrBat = System.IO.Path.Combine("C:\\Tools", "demo.bat");
var otherProcessInfo = new ProcessStartInfo("cmd.exe", $"/c call \"{strPathToExeOrBat\"");
var otherProcess = Process.Start(otherProcessInfo);
otherProcess.WaitForExit();
otherProcess.Close();
I also think it would be helpful to review the capabilities of the cmd.exe application.
I found this post to be helpful:
https://stackoverflow.com/questions/515309/what-does-cmd-c-mean#:~:text=%2FC%20Carries%20out%20the%20command%20specified%20by%20the%20string%20and,switches%20by%20typing%20cmd%20%2F%3F%20.
In particular the /k option will leave the window open, if you don't want it to close after running a script.
Thank you very much for your question! It really helped me find the answer to this! (at least for my situation of a .NET MAUI windows app, but MAUI is built off of Xamarin.Forms, so you shouldn't have a problem doing the same thing)
EDIT: Updated to use file path from question and string interpolation with System.IO.Path.Combine for slightly greater cross platform capability

Execute command like Start>Run in C#

I want to create a command executor like Start > Run. The application has a TextBox and when a user enters a command eg : notepad "C:\test.txt" it should open notepad with this file or %PROGRAMFILES% it should open 'Programs Files' directory.
For %PROGRAMFILES% and other Windows variables I can use Environment.ExpandEnvironmentVariable and get their path and pass to Process.Start
and for notepad, I can split the with space and first part goes in FileName and rest of string goes in Arguments of ProcessStartInfo.
But what I want to know is, how does Start > Run work ? is there something like I can execute the whole command without splitting command-line or expanding the variables ? Maybe with pinvoke ?
To the very best of my knowledge, the run dialog is implemented with a call to ShellExecuteEx. You can achieve the same with Process.Start and UseShellExecute. You do need to expand the environment variables, and split the command into filename and arguments. You already describe how to do that in your question, and, again to the best of my knowledge, there is no programmatic interface to the functionality of the Run dialog.
However, what you can do programmatically is show the Run dialog. Create an instance of the Shell object requesting the IShellDispatch interface, and then call the FileRun method.

Generate an .exe file with embedded settings

I have a window application which performs some tasks, One of which is opening some files and we all know .net provides exe file for the application, which can be used as click to start.
I am calling this application application1.
Now I want to generate one more window application(simple exe), let us call it application2, which will open a form with some options(say the names of the files to be opened by application1) and a generate button.
On clicking the generate button, it should generate the exe file for the application1 with the data passed from application2.
Please suggest how can I do it.
EDIT
I need to generate exe which will be available on different systems which will perform some task on regular intervals. and the interval colud be different for different computers.
so I am asked to generate which will accept the time interval and will generate the exe for that interval
There are a number of ways to consider doing this:
use a reg key with the name of the settings file to read in, and then store the settings you write from app2, for app1 in the file, so app 1 can run it
you call app1 with a parameter with either the name of a file, or commandline parameters, and it updates its own applications settings file.
put the settings in a database, so any copy of app1 anywhere can find it, assuming all users would be able to see the db server
if app1 is always to be running while app2 is you could go with some interprocess communication but probably this is the more complex of the 4
Rather than recompiling an exe, it would make sense to have a config file that goes with.
Failing that, compiling .net is only that, you can have an exe that generates a .cs file (or updates one, and reruns the whole compile and outputs an exe.. take a google, on command line compilation) but I wouldnt be my choice.
I think what your looking for is for application1 to be able to receive command line arguments and application2 to allow you to pick files and run application1 and pass in those arguments.
I don't think its wish to be generating .exe's
Another way, although not that easy, would be to
write application 1 to try and read settings from class that does not exist, say SettingsOverride, by reflection; if not found, fall back to its own hard-coded settings
application 2 uses CodeDOM or similar to create a new assembly that provides a SettingsOverride class with the new saved settings
application 2 uses ILMerge to build the new .exe from application 1 and the settings assembly; the reflection code in application 1 should now pick up the new settings.
It's probably also possible to do this with embedded resources, though I'm not sure how. Finally you could put a string constant in your .exe, say 400 X characters, and then application 2 could scan the file to find that (as Unicode/UTF-16 text) and replace that with a string containing the new settings - but I'm not 100% if you then need to recompute a checksum or similar.

Include a batch file in a batch file

I have a problem calling a batch file from another batch file when trying to run everything by using Process.Start. Basically I call the execution of a batch file from my c# program that looks like this:
call include.bat
//execute the rest of the batch file here
The include.bat file sets up paths and can be used by a number of other batch files. When I run the Process.Start sometimes this works and sometimes I get ERROR: cannot find include.bat. First of all any idea why this happens? And ideas on how to fix this from the batch file?
To switch to the directory your batch file is located in, use this:
cd %~dp0
I do this in almost all of my batch scripts. That way relative paths should always work.
I know this is an old question but I thought it would be worth noting that the approach promoted by the accepted answer (i.e. changing the working directory) may not always be appropriate.
A better general approach is to refer to dependencies by full path:
call "%~dp0include.bat"
(Since %~dp0 already ends with a backslash, we don't need to add another one.)
Here are some benefits of not changing the working directory:
The rest of the batch file can still use the original working directory.
The original working directory in the command prompt is preserved, even without "SETLOCAL".
If the first batch file is run via a UNC path (such as "\\server\share\file.bat"), the full-path call will succeed while changing the directory (even with "cd /d") will fail. (Using pushd/popd would handle this point, but they have their own set of problems.)
These benefits are particularly important for alias-type batch files, even if they are not as important for the specific situation that motivated this question.
Before the script, try CD /D %~dp0
First thing I'd try is to use full path information in the call statement for include.bat. If that fixes it, you probably are just not running the batch file from the proper location. I'm sure there's a "working directory" capability in C#, I'm just not sure what it is.
Do you set ProcessStartInfo.WorkingDirectory ( http://msdn.microsoft.com/en-us/library/system.diagnostics.processstartinfo.workingdirectory.aspx ) on the ProcessStartInfo that you pass to Process.Start?
Since include.bat sometimes cannot be found, working directory may be wrong (not the folder where include.bat is located).

access denied trying extracting an archive on the windows user temp folder

I'm trying to run a command-line process (which is extraction of a .7z archive) on a file that lies in a temporary folder on the windows user temp directory
(C:\Documents and Settings\User\Local Settings\Temp), using Process in my c# app.
I think the process return error that happens because of "access denied" because I can see a win32Exception with error code 5 when I dig in the prcoess object of .NET.
doing the same on some other location worked fine before, so I guess maybe it's something I'm not supposed to do ? (running a process to use a file on the the %TEMP%)
perhaps I need to pass security somehow?
Assuming that you are using regular .NET (not CF/Silverlight, etc) Accessing files in the user's temp area is entirely expected. I wonder if the problem isn't more that you've accidentally left the file open after creating it, perhaps by not using a "using" or similar?
I probably wouldn't suggest using environment variables (%TEMP% etc) when shelling out to a separate process; ideally you'd pass the full path to the file (less things to get wrong...), making sure to quote any path arguments (in case of space) - i.e. so your args are #"... ""c:\some path\whatever\tmp""..." (if you see what I mean).
Finally, if you are extracting files, you need to think about the existing contents. Path.GetTempFileName() is fine for creating a single file place-holder, but for extracting an archive you probably want to create a directory - guids are handy for this purpoes (while avioding conflicts, and remember to remove it afterwards):
string dir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
running the same process using command-line (cmd) helped to figure out my problem was that I specified path arguments to the process using long-path-name.
Solution to this can be found here:
standard way to convert to short path in .net

Categories