Imports System.Text Imports System.Threading Namespace Framework Public Class ProcessFile Public cArguments As String = "" Public cDomain As String = "" Public cError As String = "" Public cFileName As String = "" Public cLog As String = "" Public cMessage As String = "" Public cOutput As String = "" Public cPassword As String = "" Public cUsername As String = "" Public cWorkingDirectory As String = "" Public lReadError As Boolean = True Public lRedirectStandardError As Boolean = True Public lRedirectStandardInput As Boolean = True Public lRedirectStandardOutput As Boolean = True Private oApp As Framework.App = Nothing Private oLXProcess As Framework.LXProcess = Nothing Private oLogMutex As Mutex = New Mutex() Private oStringBuilderError As StringBuilder = New StringBuilder Private oStringBuilderOutput As StringBuilder = New StringBuilder ' This is when we access the class in a desktop mode Public Sub New(ByVal toApplication As Framework.App) oApp = toApplication End Sub ' This is when we access the class in a Web or Web Service mode Public Sub New(ByVal toProcess As Framework.LXProcess) oLXProcess = toProcess oApp = oLXProcess.oApp End Sub ' As you have already discovered, especially when used within an app, the WinZip Command Line Add-on will likely encounter ' an error discerning the handle if STDOUT is being redirected while STDERR is not being redirected. ' I have not seen any issue with refraining from redirecting the input. ' I am not qualified to advise you on programming in .NET, but if you can direct the STDERR pipe to some sort of null ' file while redirecting STDOUT to something useful, you should be able to avoid this issue. ' If you redirect using ">" you are only redirecting the STDOUT pipe. In the DOS syntax, you would need to use something similar to: ' wzzip file.zip [files] 1>out.txt 2>NUL ' The first (1>out.txt) would be the redirection of STDOUT containing useful information and the second (2>NUL) would be the ' redirection of STDERR containing only garbage information, such as the "dots" used as a type of progress meter. ' Execute an executable Public Function Process() As Boolean Dim loSecureString As System.Security.SecureString = New System.Security.SecureString() Dim lnCounter As Integer = 0 ' Reset the values cError = "" cLog = "" cMessage = "" cOutput = "" ' Trim everything cArguments = Trim(cArguments) cDomain = Trim(cDomain) cPassword = Trim(cPassword) cUsername = Trim(cUsername) ' If we have a working directory If cWorkingDirectory.Length > 0 Then ' If the directory does not exist If Not oApp.DirectoryExist(cWorkingDirectory) Then cMessage = "The directory " + cWorkingDirectory + " does not exist." Return False End If End If ' If the file does not exist If Not oApp.FileExist(cFileName) Then cMessage = "The file " + cFileName + " does not exist." Return False End If ' Use the Using\End Using approach to make sure the memory is released no matter what. This covers an unexpected error ' before it reaches the end. It closes the resources automatically at the end. Using loProcess As New Process loProcess.StartInfo.FileName = cFileName loProcess.StartInfo.WorkingDirectory = cWorkingDirectory loProcess.StartInfo.Arguments = cArguments loProcess.StartInfo.RedirectStandardOutput = lRedirectStandardOutput ' Stream the output to an event AddHandler loProcess.OutputDataReceived, AddressOf OutputDataHandler loProcess.StartInfo.RedirectStandardInput = lRedirectStandardInput loProcess.StartInfo.RedirectStandardError = lRedirectStandardError ' Stream the error to an event AddHandler loProcess.ErrorDataReceived, AddressOf ErrorDataHandler loProcess.StartInfo.UseShellExecute = False loProcess.StartInfo.WindowStyle = ProcessWindowStyle.Hidden loProcess.StartInfo.CreateNoWindow = True ' If we have a domain If cDomain.Length > 0 Then loProcess.StartInfo.Domain = cDomain End If ' If we have a username If cUsername.Length > 0 Then loProcess.StartInfo.LoadUserProfile = True loProcess.StartInfo.UserName = cUsername ' For each character For lnCounter = 1 To cPassword.Length loSecureString.AppendChar(cPassword(lnCounter - 1)) Next loProcess.StartInfo.Password = loSecureString End If Try loProcess.Start() Catch loError As Exception cMessage = loError.Message + " " + cFileName + " " + cArguments Return False End Try ' Start the asynchronous read of the error stream loProcess.BeginErrorReadLine() ' Start the asynchronous read of the output stream loProcess.BeginOutputReadLine() ' Wait for the process to complete before proceeding loProcess.WaitForExit() ' Dump the log to the error cError = oStringBuilderError.ToString ' Dump the log to the output cOutput = oStringBuilderOutput.ToString ' If we have an output If cOutput.Length > 0 Then ' If we have at least three characters If cOutput.Length > 2 Then ' If the last characters is a carriage return If Mid(cOutput, cOutput.Length - 1, 2) = oApp.cCR Then cOutput = Mid(cOutput, 1, cOutput.Length - 2) End If End If ' Some output contains only CHR(13), so we will do some parsing to standardize all this cOutput = oApp.StrTran(cOutput, oApp.cCR, "LXFramework" + Chr(0) + "LXFramework") cOutput = oApp.StrTran(cOutput, Chr(13), oApp.cCR) cOutput = oApp.StrTran(cOutput, "LXFramework" + Chr(0) + "LXFramework", oApp.cCR) End If ' If we have an output If cOutput.Length > 0 Then cLog = cLog + "Output" + oApp.cCR + "------" + oApp.cCR + cOutput End If End Using ' Reset the values cDomain = "" cPassword = "" cUsername = "" Return True End Function ' Collect the output, displaying it to the screen and logging it to the output file ' Cancel the read operation when the maximum line limit is reached Private Sub OutputDataHandler(toProcess As Object, toOutLine As DataReceivedEventArgs) ' If we have something to write If Not String.IsNullOrEmpty(toOutLine.Data) Then oLogMutex.WaitOne() ' Add to the log oStringBuilderOutput.Append(toOutLine.Data + oApp.cCR) oLogMutex.ReleaseMutex() End If End Sub ' Collect the error output, displaying it to the screen and logging it to the output file ' Cancel the error output read operation when the maximum line limit is reached Private Sub ErrorDataHandler(toProcess As Object, toErrLine As DataReceivedEventArgs) ' If we have something to write If Not String.IsNullOrEmpty(toErrLine.Data) Then oLogMutex.WaitOne() ' Add to the log oStringBuilderError.Append(toErrLine.Data + oApp.cCR) oLogMutex.ReleaseMutex() End If End Sub End Class End NamespaceSome of it contains some framework related references but this should be pretty much easy to follow. This approach comes initially from MSDN where the situation is that both streams (output and error) have to be dealt with separately. So, each has its own thread. Thus, this approach eliminates the conflict with some utilities, such as WZZip.exe, when executed from an app, such as here, will cause a freeze during the zip when it reaches about 13.2 MB. This is a bug confirmed by WinZip and so far that is the only workaround. However, having done it, the adjustment, makes the class universal for any other related situations.
[10/07/2012 13:25:49] StdOut> WinZip(R) Command Line Support Add-On Version 3.2 (Build 9715) [10/07/2012 13:25:49] StdOut> Copyright (c) 1991-2011 WinZip International LLC - All Rights Reserved [10/07/2012 13:25:49] StdOut> Adding 0005504N6403.prn [10/07/2012 13:25:49] StdOut> Adding 0005504N640301.dat [10/07/2012 13:25:49] StdOut> Adding 0005504N640301.jpg [10/07/2012 13:25:50] StdOut> Adding 0005504N640302.jpg [10/07/2012 13:25:50] StdOut> Adding 0005504N640303.jpg [10/07/2012 13:25:51] StdOut> Adding 0005504N640304.jpg [10/07/2012 13:25:51] StdOut> Adding 0005504N640305.jpg [10/07/2012 13:25:51] StdOut> Adding 0005504N640306.jpg [10/07/2012 13:25:52] StdOut> Adding 0005504N640307.jpg [10/07/2012 13:25:52] StdOut> Adding 0005504N640308.jpg [10/07/2012 13:25:52] StdOut> Adding 0005504N640309.jpg [10/07/2012 13:25:52] StdOut> Adding 0005504N640310.jpg [10/07/2012 13:25:52] StdOut> Adding 0005504N640311.jpg [10/07/2012 13:25:52] StdOut> Adding 0005504N640312.jpg [10/07/2012 13:25:52] StdOut> Adding 0005504N640313.jpg [10/07/2012 13:25:52] StdOut> Adding 0005504N640314.jpg [10/07/2012 13:25:52] StdOut> Adding 0005504N640315.jpg [10/07/2012 13:25:52] StdOut> Adding 0005504N640316.jpg [10/07/2012 13:25:52] StdOut> Adding 0005504N640317.jpg [10/07/2012 13:25:52] StdOut> Adding 0005504N640318.jpg [10/07/2012 13:25:52] StdOut> Adding 0005504N640319.jpg [10/07/2012 13:25:52] StdOut> Adding 0005504N640320.jpg [10/07/2012 13:25:53] StdOut> Adding 0005504N640321.jpg [10/07/2012 13:25:53] StdOut> Adding 0005504N640322.jpg [10/07/2012 13:25:53] StdOut> Adding 0005504N640323.jpg [10/07/2012 13:25:53] StdOut> Adding 0005504N640324.jpg [10/07/2012 13:25:53] StdOut> Adding 0005504N640325.jpg [10/07/2012 13:25:53] StdOut> Adding 0005504N640326.jpg [10/07/2012 13:25:53] StdOut> Adding 0005504N640327.jpg [10/07/2012 13:25:53] StdOut> Adding 0005504N640328.jpg [10/07/2012 13:25:53] StdOut> Creating Zip file h:\test.zip [10/07/2012 13:25:53] StdErr> Searching... ... .. ..... .. ....... .. ...............................As you can see, during its package, when it builts it up, closing around 13.2 MB will have the exe switch into a StdErr mode waiting or searching indefinitely for something, which is the problem, a bug confirmed by WinZip, and this takes care of it.