PDA

View Full Version : Problem with "custom" hiddenDosCommand


Norman3D
06-09-2011, 01:01 AM
Hey guys!

I really need your help on this one.

So, let's see. Basically what I'm trying to do is to pass commands to dos through maxscript.
So as you know there are a couple of ways to do this:

ShellLaunch
DosCommand
HiddenDosCommand

ShellLaunch and DosCommand are a problem for two reasons. First they are not hidden, which is bad :P. But they also don't work at all, when I have to pass multiple strings. Someone in the forums confirmed this, I read it not too long ago, but I couldn't find the post.
HiddenDosCommand should not be a problem, but, it doesn't seem to work properly with 3dsMax 9. Yes I have AVG installed, but it's giving me a CreateProcess error. I also noticed when uninstalling TexTools from a 3dsMax 9 installation, the same error pops up when it attempts to delete folders.

So my solution (or so I thought), was to grab this guys function:
http://mmw2008.5d6d.com/archiver/tid-16583.html
and change it a bit so it matches my needs:

fn HiddenDotNetCommand Command=
(
Process = DotNetObject "System.Diagnostics.Process"
Process.StartInfo.WindowStyle = (DotNetClass "System.Diagnostics.ProcessWindowStyle").Hidden
Process.StartInfo.CreateNoWindow = true
Process.StartInfo.UseShellExecute = false
Process.StartInfo.RedirectStandardInput = true;
Process.StartInfo.RedirectStandardOutput = true;
Process.StartInfo.FileName = "cmd.exe"
Process.Start()
Process.StandardInput.WriteLine(Command)
Process.StandardInput.WriteLine("exit")
Process.WaitForExit()
)

Now I have tested it a couple of times here.
Windows 7 - 64bit
all 3dsMax versions, 3dsMax 9 SP2 - 3dsMax 2012
I have also tested it on another PC:
Windows 32-bit 3dsMax 9 SP2

In both cases it works 100%


But, now I have had two reports, where I have been able to confirm that this function does not work!
The specs of the PCs were:
-Windows 7 - 64bit, 3dsMax 2012
-Windows XP - 32bit, 3dsMax 2011
Since I don't have access to those PCs it's hard for me to debug, I also don't know enough dotnet yet to be able to understand what might be wrong.

Do you guys have any idea why this would not work on some PCs? :/

denisT
06-09-2011, 06:09 AM
But, now I have had two reports, where I have been able to confirm that this function does not work!
The specs of the PCs were:
-Windows 7 - 64bit, 3dsMax 2012
-Windows XP - 32bit, 3dsMax 2011

Do you guys have any idea why this would not work on some PCs? :/

i don't see any problem in the code. i think that the dos command itself doesn't execute right on those machines for any reason. ask the person who reported the problem to executed the command directly using the command prompt window, and give you a result.

biddle
06-09-2011, 06:35 AM
I ran into problems handling error output that was returned by the Perforce command line processor. I ran into the kinds of deadlock issue discussed here reading stderr (http://msdn.microsoft.com/en-us/library/system.diagnostics.process.standarderror.aspx)

I eventually gave up on a pure MXS solution and started using asynchronous reads through a delegate in a C# snippet:

fn CreateDotNetProcessor =
(
if dotnet.GetType "DotNetProcessor" == undefined do
(
format "Constructing .NET Processor...\n"
-- If layout of this text looks ragged, press ctrl-F11 to switch to a monospaced font...
sb = ""
sb +=" using System; \n"
sb +=" using System.Text; \n"
sb +=" using System.Diagnostics; \n"
sb +=" \n"
sb +=" class DotNetProcessor \n"
sb +=" { \n"
sb +=" \n"
sb +=" private static StringBuilder stdOut = null; \n"
sb +=" private static StringBuilder stdErr = null; \n"
sb +=" \n"
sb +=" public String Output() { return stdOut == null ? \"\" : stdOut.ToString(); } \n"
sb +=" public String Errors() { return stdErr == null ? \"\" : stdErr.ToString(); } \n"
sb +=" \n"
sb +=" public void Execute(String filename, String args, String stdinbuff) \n"
sb +=" { \n"
sb +=" Process p = new System.Diagnostics.Process(); \n"
sb +=" \n"
sb +=" stdErr = new StringBuilder(); \n"
sb +=" stdOut = new StringBuilder(); \n"
sb +=" \n"
sb +=" p.EnableRaisingEvents = true; \n"
sb +=" p.StartInfo.FileName = filename; \n"
sb +=" p.StartInfo.Arguments = args; \n"
sb +=" p.StartInfo.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden ; \n"
sb +=" p.StartInfo.UseShellExecute = false; \n"
sb +=" p.StartInfo.RedirectStandardOutput = true; \n"
sb +=" p.StartInfo.RedirectStandardError = true; \n"
sb +=" p.StartInfo.RedirectStandardInput = stdinbuff.Length > 0; \n"
sb +=" \n"
sb +=" p.StartInfo.CreateNoWindow = true; \n"
sb +=" \n"
sb +=" p.OutputDataReceived += new DataReceivedEventHandler(OutputHandler); \n"
sb +=" p.ErrorDataReceived += new DataReceivedEventHandler(ErrorHandler); \n"
sb +=" \n"
sb +=" p.Start(); \n"
sb +=" \n"
sb +=" if (stdinbuff.Length > 0) \n"
sb +=" { \n"
sb +=" p.StandardInput.Write(stdinbuff); \n"
sb +=" p.StandardInput.Close(); \n"
sb +=" } \n"
sb +=" \n"
sb +=" stdErr = new StringBuilder(); \n"
sb +=" stdOut = new StringBuilder(); \n"
sb +=" \n"
sb +=" p.BeginOutputReadLine(); \n"
sb +=" p.BeginErrorReadLine(); \n"
sb +=" \n"
sb +=" p.WaitForExit(); \n"
sb +=" p.Close(); \n"
sb +=" } \n"
sb +=" \n"
sb +=" private static void OutputHandler(object sendingProcess, DataReceivedEventArgs d) \n"
sb +=" { \n"
sb +=" if (!String.IsNullOrEmpty(d.Data)) stdOut.Append(d.Data + \"\\n\"); \n"
sb +=" } \n"
sb +=" \n"
sb +=" private static void ErrorHandler(object sendingProcess, DataReceivedEventArgs d) \n"
sb +=" { \n"
sb +=" if (!String.IsNullOrEmpty(d.Data)) stdErr.Append(d.Data + \"\\n\"); \n"
sb +=" } \n"
sb +=" } \n"

csharpProvider = dotnetobject "Microsoft.CSharp.CSharpCodeProvider"
compilerParams = dotnetobject "System.CodeDom.Compiler.CompilerParameters"

compilerParams.ReferencedAssemblies.Add("System.dll");

compilerParams.GenerateInMemory = true
compilerResults = csharpProvider.CompileAssemblyFromSource compilerParams #(sb)

if (compilerResults.Errors.Count > 0 ) then
(
errs = stringstream ""
for i = 0 to (compilerResults.Errors.Count-1) do
(
err = compilerResults.Errors.Item[i]
format "Error:% Line:% Column:% %\n" err.ErrorNumber err.Line \
err.Column err.ErrorText to:errs
)
MessageBox (errs as string) title: "Errors encountered while compiling C# code"
format "%\n" errs
return undefined
)
)
dotnetobject "DotNetProcessor"
)

-- Tests
(
dnp = CreateDotNetProcessor()

-- Run a dir command
dnp.execute "cmd.exe" "/K dir c:\\" ""
format "output:\n%" (dnp.output())
format "errors:\n%" (dnp.errors())

-- Read from stdin
dnp.execute "cmd.exe" "" "echo foo\necho fee\n"
format "output:\n%" (dnp.output())
format "errors:\n%" (dnp.errors())

-- Launch an app and wait for it to complete
dnp.execute "calc.exe" "" ""
format "output:\n%" (dnp.output())
format "errors:\n%" (dnp.errors())

-- Do some ftp fiddling (using stdin)
dnp.execute "ftp" "" "status\n! echo \"Escape to the shell!\n!dir c:\\\"\nstatus\nquit"
format "output:\n%" (dnp.output())
format "errors:\n%" (dnp.errors())

-- Query Perforce (if you have it)
dnp.execute "p4" "info" ""
format "output:\n%" (dnp.output())
format "errors:\n%" (dnp.errors())
)


It's not pretty, but it stopped hanging...
.biddle

[edit: missing a semi-colon]

kees
06-09-2011, 03:35 PM
Mike Biddlecombe
Animation Programmer
United Front Games
Vancouver, Canada


I didn't know you were in Vancouver these days Mike!

Norman3D
06-09-2011, 03:57 PM
i don't see any problem in the code. i think that the dos command itself doesn't execute right on those machines for any reason. ask the person who reported the problem to executed the command directly using the command prompt window, and give you a result.

Yes I tried this. I'm 100% sure it has nothing to do with the command being wrong, no problems with quotation or anything like that.


I ran into problems handling error output that was returned by the Perforce command line processor. I ran into the kinds of deadlock issue discussed here reading stderr (http://msdn.microsoft.com/en-us/library/system.diagnostics.process.standarderror.aspx)

I eventually gave up on a pure MXS solution and started using asynchronous reads through a delegate in a C# snippet:

fn CreateDotNetProcessor =
(
if dotnet.GetType "DotNetProcessor" == undefined do
(
...


It's not pretty, but it stopped hanging...
.biddle

[edit: missing a semi-colon]


wow thanks! O_O

Do you mind helping me out one last time? I'm trying to pass this command with your function:

"C:\\md5.exe" -o"C:\md5hash.tmp" "C:\TheFile.exe"

The md5hash.tmp should be created as a result of this command.
However I'm not sure if I'm doing it right since the file is not being created:

dnp.execute ("\"" + "C:\\md5.exe" + "\"") "" ("-o" + "\"" + "C:\\md5hash.tmp" + "\"" + " " + "\"" + "C:\\TheFile.exe" + "\"")

biddle
06-09-2011, 06:29 PM
I think all you need to do is swap the 'args' that are passed to the executable and the 'stdin' input that will be sent to the program once it's up and running (which is usually not required and can be left empty)

exe = "C:\md5.exe"
args = "-o\"C:\\md5hash.tmp\" \"C:\\TheFile.exe\""
input = ""

dp.execute exe args input

biddle
06-09-2011, 06:35 PM
Mike Biddlecombe
Animation Programmer
United Front Games
Vancouver, Canada


I didn't know you were in Vancouver these days Mike!

Go Canucks Go! :)

Kameleon
06-09-2011, 08:23 PM
See if this works. Cheers!

fn HiddenDotNetCommand Command=
(
Process = DotNetObject "System.Diagnostics.Process"
Process.StartInfo.WindowStyle = (DotNetClass "System.Diagnostics.ProcessWindowStyle").Hidden
Process.StartInfo.FileName = "cmd.exe"
Process.StartInfo.Arguments = "/C " + Command
Process.Start()
)

Norman3D
06-12-2011, 03:20 PM
See if this works. Cheers!

fn HiddenDotNetCommand Command=
(
Process = DotNetObject "System.Diagnostics.Process"
Process.StartInfo.WindowStyle = (DotNetClass "System.Diagnostics.ProcessWindowStyle").Hidden
Process.StartInfo.FileName = "cmd.exe"
Process.StartInfo.Arguments = "/C " + Command
Process.Start()
)

Hey Kameleon, just a quick update. Seems like your code, just like the one I was using, is not working on the guy's machine. But biddle's code seems to work 100% of time across all machines so far.

Kameleon
06-12-2011, 06:16 PM
How about if you remove the StartInfo ? Just run the Process.Start ("cmd.exe /C " + Command) ?

Norman3D
06-12-2011, 06:31 PM
How about if you remove the StartInfo ? Just run the Process.Start ("cmd.exe /C " + Command) ?

I tried this...

fn HiddenDotNetCommand Command=
(
Process = DotNetObject "System.Diagnostics.Process"
Process.StartInfo.WindowStyle = (DotNetClass "System.Diagnostics.ProcessWindowStyle").Hidden
Process.Start(Command)
)

HiddenDotNetCommand ("cmd.exe /C " + "\"" + getDir #userscripts + "\\md5.exe" + "\"" + " " + "-o\"" + "C:\\md5hash.tmp" + "\"" + " " + "\"" + (getDir #userscripts + "\\theFile.exe") + "\"")

and I get the following error:
-- Runtime error: dotNet runtime exception: The system cannot find the file specified

Pjanssen
06-12-2011, 06:48 PM
Can't you start the md5.exe process directly using Process.Start?
Or if you're specifically after MD5, try this: http://msdn.microsoft.com/en-us/library/system.security.cryptography.md5.aspx

Norman3D
06-12-2011, 06:51 PM
Can't you start the md5.exe process directly using Process.Start?
Yes this works, but trying to pass arguments directly through Process.Start instead of using Process.StartInfo.Arguments fails.

I have no problem using biddle's code, it really does seem to work :)

Norman3D
06-12-2011, 06:55 PM
Or if you're specifically after MD5, try this: http://msdn.microsoft.com/en-us/library/system.security.cryptography.md5.aspx
I need it for more than MD5 hash, but as a matter of fact, I tried this (http://forums.cgsociety.org/showpost.php?p=6907441&postcount=3) code by Kameleon and it seemed to work, except with 3dsMax 9, that's why I switched to command line tool. (I can't really remember what the error message was)

Kameleon
06-12-2011, 09:02 PM
Yes this works, but trying to pass arguments directly through Process.Start instead of using Process.StartInfo.Arguments fails.

I have no problem using biddle's code, it really does seem to work :)

My guess is that you have something wrong when constructing the string, I saw that you have the file between "" and the arguments out which is correct but maybe debugging that whole string again might help to find a working solution.

About the md5 function in max 9 not working maybe it's some conversion types that work differently in Max 9, I remember having those kind of problems.

Norman3D
06-12-2011, 09:22 PM
My guess is that you have something wrong when constructing the string, I saw that you have the file between "" and the arguments out which is correct but maybe debugging that whole string again might help to find a working solution.

I don't think the string was the problem. The function works fine on 95% of the machines, so the string can't be "randomly" wrong. As a matter of fact I'm using this with MaxScriptManager, and a couple hundred people have installed it successfully. Only two have reported having a problem, and I've been able to pin point that the function was the root of the problem. As to why it's causing problems in some machines. I'm absolutely clueless. :surprised

Panayot
06-13-2011, 01:52 AM
maybe VBScript can help... its works well for me and made hidden dos command compatible for older Max's (below 9)
' task.vbs
Const strCommand = "CD C:\ & Dir > test.txt"
Dim oShell : Set oShell = WScript.CreateObject ("WSCript.shell")
Call oShell.Run("CMD /C " & strCommand, 0, True)
Set oShell = Nothing
-- run it in MaxScript:
ShellLaunch "task.vbs" ""

biddle
06-13-2011, 05:15 AM
My experience with launching a process:

Many years ago I developed a 'hidden' version of the DosCommand plugin in order to get rid of the annoying dos box that appeared whenever I ran pipeline tools.

When .NET support arrived I dumped the plugin in favour of a synchronous System.Diagnostic.Process based solution. It was simple stuff: start a process, wait for it to complete, gather the redirected output and errors.

And it seemed to work just great.

...except every six months or so an artist would complain that a something referring to a 'hidden dos command' was crashing Max. The first thing I would do when I went to investigate was explain the difference between a 'crash' and a 'hang' as it always turned out that max was still running --it was just spinning in a loop waiting for a 'hidden' command to complete. :)

The second thing I'd do was verify that the machine in question had the latest version of "P4.exe", the command line tool used to interact with the Perforce server. I *always* found that P4 was several versions out of date on the suspect machines -- two to three years out of date was not unheard of! For me, updating the perforce tools was always the solution to a "HiddenDosCommand" that failed to return when accessing it. Once the artist had the latest version the deadlock went away. The problem was never with my command processing code, it was always the command!

Until the day came when a brand new machine with a perfectly up to date version of P4.exe deadlocked as well. I had to do the walk of shame back to my desk and look for a solution.

Depending on the query, P4.exe can send hundreds, even thousands of lines of information out to the stdout and stderr streams simultaneously. The wrapper functions that I had developed would submit changelists, reopen files in different changelists, create new changelists by reading from the standard input, and sending data back.

By switching to asynchronous processing of the output streams (in the manner I showed in my earlier post) I was able avoid the deadlocks I was encountering and 'it just worked'. I still start a process, wait for it complete, then collect the results, but the way output gets handed under the covers is simply more robust.

For the record my first asynchronous efforts involved using AddEventHandler #OutputDataReceived and #ErrorDataReceived to add mxs output handler functions to stderr and stdout and avoid any embedded C# --but it never worked for me. I figured it was a threading issue of some sort with the callbacks as they entered back into maxscript land. Someone else may be able to get the AddEventHandler route to work, but I'd probably not trust it.

[...and before someone gets started, yes I know there are .NET tools for Perforce that access it through its API that would probably never have had this deadlock problem, but my solution was 100% mxs (still is, if you allow embedded C# :) ) and I was perhaps perversely proud of the fact that there was nothing extra to install into Max in order to query p4.]

.biddle

Panayot
06-13-2011, 04:58 PM
...and I was perhaps perversely proud of the fact that there was nothing extra to install into Max in order to query p4.]

.biddle
Interesing... i has not p4 and not met any problems with Max hiddenDosCommand,
so ... can we draw a conclusion that this is just Perforce issue ?

Norman3D
06-13-2011, 05:15 PM
Interesing... i has not p4 and not met any problems with Max hiddenDosCommand,
so ... can we draw a conclusion that this is just Perforce issue ?
nop definitely not a P4 issue only. I believe it has more to do with the fact that you are attempting to pass multiple strings to the command. And for whatever reason this seems to cause problems on some machines.

maybe VBScript can help... its works well for me and made hidden dos command compatible for older Max's (below 9)
' task.vbs
Const strCommand = "CD C:\ & Dir > test.txt"
Dim oShell : Set oShell = WScript.CreateObject ("WSCript.shell")
Call oShell.Run("CMD /C " & strCommand, 0, True)
Set oShell = Nothing
-- run it in MaxScript:
ShellLaunch "task.vbs" ""
This would also not be an elegant solution. You could as well write bat files with the commands on-the-fly and then run the bat files through hiddenDosCommand, which in this case would work flawlessly. It's only when the command needs multiple strings that stuff starts to break.

So yeah, just to sum it up, read biddle's posts. Biddle's code seems to be the only one that works so far 100% of the time.

Kameleon
06-13-2011, 05:47 PM
Another thought... not that I have a reasonable explanation for this but have you tried running the hiddenDosCommand in a different thread using BackgroundWorker? Dunno... just a wild idea :D

Norman3D
06-13-2011, 05:54 PM
Another thought... not that I have a reasonable explanation for this but have you tried running the hiddenDosCommand in a different thread using BackgroundWorker? Dunno... just a wild idea :D
hehe no problem. Nop, I haven't tried it. It is also hard for me to test, since my PC is not one of the "mystery PCs" having the problem. But haavard (http://forums.cgsociety.org/member.php?u=235768) does have the problem on his machine, perhaps he can test it for you. ;)

Panayot
06-13-2011, 07:54 PM
nop definitely not a P4 issue only. I believe it has more to do with the fact that you are attempting to pass multiple strings to the command. And for whatever reason this seems to cause problems on some machines.


This would also not be an elegant solution. You could as well write bat files with the commands on-the-fly and then run the bat files through hiddenDosCommand, which in this case would work flawlessly. It's only when the command needs multiple strings that stuff starts to break.

So yeah, just to sum it up, read biddle's posts. Biddle's code seems to be the only one that works so far 100% of the time.
Thanks for the reply. I'm sure Biddle done good work, just want to imagine if I needs his code.

But I not understand why you need bat files and hiddenDosCommand for VB. I send (one or multiple) DOS commands (in one go) with Run method using built-in WSH engine (Windows Scripting Host) and 'execute' my VB in Max with ShellLaunch.

About passing multiple strings to the command, am not sure, maybe you are right. I guess though the *length* of the strings maybe is more critical. (large directory paths for instance)

It's not about who is right but to learn something (at least me) ;)

Norman3D
06-13-2011, 08:00 PM
Thanks for the reply. I'm sure Biddle done good work, just want to imagine if I needs his code.

But I not understand why you need bat files and hiddenDosCommand for VB. I send (one or multiple) DOS commands (in one go) with Run method using built-in WSH engine (Windows Scripting Host) and 'execute' my VB in Max with ShellLaunch.

About passing multiple strings to the command, am not sure, maybe you are right. I guess though the *length* of the strings maybe is more critical. (large directory paths for instance)

It's not about who is right but to learn something (at least me) ;)

yeah! I totally agree!
What I meant to say regarding the VB files, I'm not sure how that works exactly, it sounds like you need to have certain elements in your machine in order for it to work. And if you have to write a file and then launch it trough ShellLaunch you might as well just write the commands you want in a text file, rename to a bat file and run them through hiddenDosCommand. Since you won't be passing any arguments to the "temp" bat file hiddenDosCommand will work properly with 3dsMax 9.

Yes perhaps it is the length of the string that is an issue.

Norman3D
06-13-2011, 10:48 PM
Ok biddle, I'm having yet another problem with your DotNetProcessor. I tried PMing you but your inbox is full, so I'm just going to post it here in hopes that you read this. Perhaps others can help me out as well.

I'm using a tool called zipnote.exe by the same guys who did "maxzip.exe" (you can find it in 3dsMax root folder).
So zipnote allows me to "extract" and add comments to zip files. If you open a zip file with winrar, you'll be able to see the comments on the right side.

So anyway, at one point in my script I extract the comments as a text file and attempt to add it back again. This would be the dos command for it:

zipnote.exe -w "theZipFile.zip" < "theComment.txt"

Now this is what I'm trying but does not seem to work:
theFile = getDir #userscripts + "\\GoMax.zip"
theCommentFile = getDir #userscripts + "\\test.txt"

theZipNote = getDir #userscripts + "\\zipnote.exe"

dnp = CreateDotNetProcessor()
dnp.execute theZipNote ("-w " + "\"" + theFile + "\"" + " < " + "\"" + theCommentFile + "\"") ""
I think the issue is with the arguments somehow. ZipNote is being executed, but the arguments are not passed correctly, the tool is telling me that I can only pick one zip file, which obviously does not make any sense. Perhaps some characters, (the quotes maybe?) are not being passed properly.

biddle
06-14-2011, 05:00 PM
The '<' in your sample command line would normally redirect input from a file, if we were actually running in a dos shell. The basic processor I showed passes data to the standard input of the exe using a string. You could go back and modify the C# processor code to read from a file, or you can read your comments in from the file and pass them along. This is probably a better option if you are writing out that comment file somewhere else in your code just so you can read it back here:

Something like:

theFile = (getdir #userscripts) + "\\GoMax.zip"
theCommentFile = (getdir #userscripts) + "\\text.txt"
theZipnote = (getdir #userscripts) + "\\zipnote.exe"

theArgs = "-w \"" + theFile + "\""

-- Read the contents of the comment file into a string that can be passed as an argument.
stream = (dotnetobject "System.IO.StreamReader" theCommentFile)
theComments = stream.ReadToEnd()
stream.close()

dnp.execute theZipNote theArgs theComments

.biddle

Norman3D
06-16-2011, 09:47 PM
Awesome! Thanks biddle!

CGTalk Moderation
06-16-2011, 09:47 PM
This thread has been automatically closed as it remained inactive for 12 months. If you wish to continue the discussion, please create a new thread in the appropriate forum.