瀏覽代碼

Refactor ShellStream to add BeginExpect
Fix Shell support in Silverlight and WindowsPhone applications

olegkap_cp 13 年之前
父節點
當前提交
2c424f6256

+ 5 - 1
Renci.SshClient/Renci.SshNet.NET35/Renci.SshNet.NET35.csproj

@@ -225,6 +225,9 @@
     <Compile Include="..\Renci.SshNet\ExpectAction.cs">
       <Link>ExpectAction.cs</Link>
     </Compile>
+    <Compile Include="..\Renci.SshNet\ExpectAsyncResult.cs">
+      <Link>ExpectAsyncResult.cs</Link>
+    </Compile>
     <Compile Include="..\Renci.SshNet\ForwardedPort.cs">
       <Link>ForwardedPort.cs</Link>
     </Compile>
@@ -809,6 +812,7 @@
     </Compile>
     <Compile Include="Channels\ChannelDirectTcpip.NET35.cs" />
     <Compile Include="Common\Extensions.NET35.cs" />
+    <Compile Include="ShellStream.NET35.cs" />
     <Compile Include="ForwardedPortDynamic.NET35.cs" />
     <Compile Include="ForwardedPortLocal.NET35.cs" />
     <Compile Include="ForwardedPortRemote.NET35.cs" />
@@ -823,7 +827,7 @@
   <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
   <ProjectExtensions>
     <VisualStudio>
-      <UserProperties ProjectLinkReference="2f5f8c90-0bd1-424f-997c-7bc6280919d1" ProjectLinkerExcludeFilter="\\?desktop(\\.*)?$;\\?silverlight(\\.*)?$;\.desktop;\.silverlight;\.xaml;^service references(\\.*)?$;\.clientconfig;^web references(\\.*)?$" />
+      <UserProperties ProjectLinkerExcludeFilter="\\?desktop(\\.*)?$;\\?silverlight(\\.*)?$;\.desktop;\.silverlight;\.xaml;^service references(\\.*)?$;\.clientconfig;^web references(\\.*)?$" ProjectLinkReference="2f5f8c90-0bd1-424f-997c-7bc6280919d1" />
     </VisualStudio>
   </ProjectExtensions>
   <!-- To modify your build process, add your task inside one of the targets below and uncomment it. 

+ 16 - 0
Renci.SshClient/Renci.SshNet.NET35/ShellStream.NET35.cs

@@ -0,0 +1,16 @@
+using System;
+using System.Threading;
+
+namespace Renci.SshNet
+{
+    /// <summary>
+    /// Represents instance of the SSH shell object
+    /// </summary>
+    public partial class ShellStream
+    {
+        partial void ExecuteThread(Action action)
+        {
+            ThreadPool.QueueUserWorkItem((o) => { action(); });
+        }
+    }
+}

+ 6 - 1
Renci.SshClient/Renci.SshNet.Silverlight/Renci.SshNet.Silverlight.csproj

@@ -213,6 +213,9 @@
     <Compile Include="..\Renci.SshNet\ExpectAction.cs">
       <Link>ExpectAction.cs</Link>
     </Compile>
+    <Compile Include="..\Renci.SshNet\ExpectAsyncResult.cs">
+      <Link>ExpectAsyncResult.cs</Link>
+    </Compile>
     <Compile Include="..\Renci.SshNet\ForwardedPort.cs">
       <Link>ForwardedPort.cs</Link>
     </Compile>
@@ -775,6 +778,8 @@
       <Link>SubsystemSession.cs</Link>
     </Compile>
     <Compile Include="Common\Extensions.SilverlightShared.cs" />
+    <Compile Include="Shell.SilverlightShared.cs" />
+    <Compile Include="ShellStream.SilverlightShared.cs" />
     <Compile Include="ForwardedPortLocal.SilverlightShared.cs" />
     <Compile Include="ForwardedPortRemote.SilverlightShared.cs" />
     <Compile Include="KeyboardInteractiveAuthenticationMethod.SilverlightShared.cs" />
@@ -792,7 +797,7 @@
       <FlavorProperties GUID="{A1591282-1198-4647-A2B1-27E5FF5F6F3B}">
         <SilverlightProjectProperties />
       </FlavorProperties>
-      <UserProperties ProjectLinkReference="2f5f8c90-0bd1-424f-997c-7bc6280919d1" ProjectLinkerExcludeFilter="\\?desktop(\\.*)?$;\\?silverlight(\\.*)?$;\.desktop;\.silverlight;\.xaml;^service references(\\.*)?$;\.clientconfig;^web references(\\.*)?$" />
+      <UserProperties ProjectLinkerExcludeFilter="\\?desktop(\\.*)?$;\\?silverlight(\\.*)?$;\.desktop;\.silverlight;\.xaml;^service references(\\.*)?$;\.clientconfig;^web references(\\.*)?$" ProjectLinkReference="2f5f8c90-0bd1-424f-997c-7bc6280919d1" />
     </VisualStudio>
   </ProjectExtensions>
   <!-- To modify your build process, add your task inside one of the targets below and uncomment it. 

+ 16 - 0
Renci.SshClient/Renci.SshNet.Silverlight/Shell.SilverlightShared.cs

@@ -0,0 +1,16 @@
+using System;
+using System.Threading;
+
+namespace Renci.SshNet
+{
+    /// <summary>
+    /// 
+    /// </summary>
+    public partial class Shell
+    {
+        partial void ExecuteThread(Action action)
+        {
+            ThreadPool.QueueUserWorkItem((o) => { action(); });
+        }
+    }
+}

+ 16 - 0
Renci.SshClient/Renci.SshNet.Silverlight/ShellStream.SilverlightShared.cs

@@ -0,0 +1,16 @@
+using System;
+using System.Threading;
+
+namespace Renci.SshNet
+{
+    /// <summary>
+    /// 
+    /// </summary>
+    public partial class ShellStream
+    {
+        partial void ExecuteThread(Action action)
+        {
+            ThreadPool.QueueUserWorkItem((o) => { action(); });
+        }
+    }
+}

+ 10 - 1
Renci.SshClient/Renci.SshNet.Silverlight5/Renci.SshNet.Silverlight5.csproj

@@ -219,6 +219,9 @@
     <Compile Include="..\Renci.SshNet\ExpectAction.cs">
       <Link>ExpectAction.cs</Link>
     </Compile>
+    <Compile Include="..\Renci.SshNet\ExpectAsyncResult.cs">
+      <Link>ExpectAsyncResult.cs</Link>
+    </Compile>
     <Compile Include="..\Renci.SshNet\ForwardedPort.cs">
       <Link>ForwardedPort.cs</Link>
     </Compile>
@@ -768,9 +771,15 @@
     <Compile Include="..\Renci.SshNet\Shell.cs">
       <Link>Shell.cs</Link>
     </Compile>
+    <Compile Include="..\Renci.SshNet.Silverlight\Shell.SilverlightShared.cs">
+      <Link>Shell.SilverlightShared.cs</Link>
+    </Compile>
     <Compile Include="..\Renci.SshNet\ShellStream.cs">
       <Link>ShellStream.cs</Link>
     </Compile>
+    <Compile Include="..\Renci.SshNet.Silverlight\ShellStream.SilverlightShared.cs">
+      <Link>ShellStream.SilverlightShared.cs</Link>
+    </Compile>
     <Compile Include="..\Renci.SshNet\SshClient.cs">
       <Link>SshClient.cs</Link>
     </Compile>
@@ -798,7 +807,7 @@
       <FlavorProperties GUID="{A1591282-1198-4647-A2B1-27E5FF5F6F3B}">
         <SilverlightProjectProperties />
       </FlavorProperties>
-      <UserProperties ProjectLinkerExcludeFilter="\\?desktop(\\.*)?$;\\?silverlight(\\.*)?$;\.desktop;\.silverlight;\.xaml;^service references(\\.*)?$;\.clientconfig;^web references(\\.*)?$" ProjectLinkReference="2f5f8c90-0bd1-424f-997c-7bc6280919d1" />
+      <UserProperties ProjectLinkReference="2f5f8c90-0bd1-424f-997c-7bc6280919d1" ProjectLinkerExcludeFilter="\\?desktop(\\.*)?$;\\?silverlight(\\.*)?$;\.desktop;\.silverlight;\.xaml;^service references(\\.*)?$;\.clientconfig;^web references(\\.*)?$" />
     </VisualStudio>
   </ProjectExtensions>
   <!-- To modify your build process, add your task inside one of the targets below and uncomment it. 

+ 13 - 0
Renci.SshClient/Renci.SshNet.Tests/Classes/PrivateKeyAuthenticationMethodTest.cs

@@ -12,6 +12,7 @@ namespace Renci.SshNet.Tests.Classes
     {
         [TestMethod]
         [TestCategory("AuthenticationMethod")]
+        [TestCategory("PrivateKeyAuthenticationMethod")]
         [Owner("Kenneth_aa")]
         [Description("PrivateKeyAuthenticationMethod: Pass null as username, null as password.")]
         [ExpectedException(typeof(ArgumentException))]
@@ -22,6 +23,18 @@ namespace Renci.SshNet.Tests.Classes
 
         [TestMethod]
         [TestCategory("AuthenticationMethod")]
+        [TestCategory("PrivateKeyAuthenticationMethod")]
+        [Owner("olegkap")]
+        [Description("PrivateKeyAuthenticationMethod: Pass valid username, null as password.")]
+        [ExpectedException(typeof(ArgumentNullException))]
+        public void PrivateKey_Test_Pass_PrivateKey_Null()
+        {
+            new PrivateKeyAuthenticationMethod("username", null);
+        }
+
+        [TestMethod]
+        [TestCategory("AuthenticationMethod")]
+        [TestCategory("PrivateKeyAuthenticationMethod")]
         [Owner("Kenneth_aa")]
         [Description("PrivateKeyAuthenticationMethod: Pass String.Empty as username, null as password.")]
         [ExpectedException(typeof(ArgumentException))]

+ 10 - 1
Renci.SshClient/Renci.SshNet.WindowsPhone/Renci.SshNet.WindowsPhone.csproj

@@ -205,6 +205,9 @@
     <Compile Include="..\Renci.SshNet\ExpectAction.cs">
       <Link>ExpectAction.cs</Link>
     </Compile>
+    <Compile Include="..\Renci.SshNet\ExpectAsyncResult.cs">
+      <Link>ExpectAsyncResult.cs</Link>
+    </Compile>
     <Compile Include="..\Renci.SshNet\ForwardedPort.cs">
       <Link>ForwardedPort.cs</Link>
     </Compile>
@@ -763,9 +766,15 @@
     <Compile Include="..\Renci.SshNet\Shell.cs">
       <Link>Shell.cs</Link>
     </Compile>
+    <Compile Include="..\Renci.SshNet.Silverlight\Shell.SilverlightShared.cs">
+      <Link>Shell.SilverlightShared.cs</Link>
+    </Compile>
     <Compile Include="..\Renci.SshNet\ShellStream.cs">
       <Link>ShellStream.cs</Link>
     </Compile>
+    <Compile Include="..\Renci.SshNet.Silverlight\ShellStream.SilverlightShared.cs">
+      <Link>ShellStream.SilverlightShared.cs</Link>
+    </Compile>
     <Compile Include="..\Renci.SshNet\SshClient.cs">
       <Link>SshClient.cs</Link>
     </Compile>
@@ -788,7 +797,7 @@
   <Import Project="$(MSBuildExtensionsPath)\Microsoft\Silverlight for Phone\$(TargetFrameworkVersion)\Microsoft.Silverlight.CSharp.targets" />
   <ProjectExtensions>
     <VisualStudio>
-      <UserProperties ProjectLinkerExcludeFilter="\\?desktop(\\.*)?$;\\?silverlight(\\.*)?$;\.desktop;\.silverlight;\.xaml;^service references(\\.*)?$;\.clientconfig;^web references(\\.*)?$" ProjectLinkReference="2f5f8c90-0bd1-424f-997c-7bc6280919d1" />
+      <UserProperties ProjectLinkReference="2f5f8c90-0bd1-424f-997c-7bc6280919d1" ProjectLinkerExcludeFilter="\\?desktop(\\.*)?$;\\?silverlight(\\.*)?$;\.desktop;\.silverlight;\.xaml;^service references(\\.*)?$;\.clientconfig;^web references(\\.*)?$" />
     </VisualStudio>
   </ProjectExtensions>
   <!-- To modify your build process, add your task inside one of the targets below and uncomment it. 

+ 10 - 1
Renci.SshClient/Renci.SshNet.WindowsPhone8/Renci.SshNet.WindowsPhone8.csproj

@@ -248,6 +248,9 @@
     <Compile Include="..\Renci.SshNet\ExpectAction.cs">
       <Link>ExpectAction.cs</Link>
     </Compile>
+    <Compile Include="..\Renci.SshNet\ExpectAsyncResult.cs">
+      <Link>ExpectAsyncResult.cs</Link>
+    </Compile>
     <Compile Include="..\Renci.SshNet\ForwardedPort.cs">
       <Link>ForwardedPort.cs</Link>
     </Compile>
@@ -806,9 +809,15 @@
     <Compile Include="..\Renci.SshNet\Shell.cs">
       <Link>Shell.cs</Link>
     </Compile>
+    <Compile Include="..\Renci.SshNet.Silverlight\Shell.SilverlightShared.cs">
+      <Link>Shell.SilverlightShared.cs</Link>
+    </Compile>
     <Compile Include="..\Renci.SshNet\ShellStream.cs">
       <Link>ShellStream.cs</Link>
     </Compile>
+    <Compile Include="..\Renci.SshNet.Silverlight\ShellStream.SilverlightShared.cs">
+      <Link>ShellStream.SilverlightShared.cs</Link>
+    </Compile>
     <Compile Include="..\Renci.SshNet\SshClient.cs">
       <Link>SshClient.cs</Link>
     </Compile>
@@ -831,7 +840,7 @@
   <Import Project="$(MSBuildExtensionsPath)\Microsoft\$(TargetFrameworkIdentifier)\$(TargetFrameworkVersion)\Microsoft.$(TargetFrameworkIdentifier).CSharp.targets" />
   <ProjectExtensions>
     <VisualStudio>
-      <UserProperties ProjectLinkReference="2f5f8c90-0bd1-424f-997c-7bc6280919d1" ProjectLinkerExcludeFilter="\\?desktop(\\.*)?$;\\?silverlight(\\.*)?$;\.desktop;\.silverlight;\.xaml;^service references(\\.*)?$;\.clientconfig;^web references(\\.*)?$" />
+      <UserProperties ProjectLinkerExcludeFilter="\\?desktop(\\.*)?$;\\?silverlight(\\.*)?$;\.desktop;\.silverlight;\.xaml;^service references(\\.*)?$;\.clientconfig;^web references(\\.*)?$" ProjectLinkReference="2f5f8c90-0bd1-424f-997c-7bc6280919d1" />
     </VisualStudio>
   </ProjectExtensions>
   <!-- To modify your build process, add your task inside one of the targets below and uncomment it. 

+ 66 - 0
Renci.SshClient/Renci.SshNet/ExpectAsyncResult.cs

@@ -0,0 +1,66 @@
+using System;
+using System.Threading;
+
+namespace Renci.SshNet
+{
+    /// <summary>
+    /// Provides additional information for asynchronous command execution
+    /// </summary>
+    public class ExpectAsyncResult : IAsyncResult
+    {
+        /// <summary>
+        /// Gets or sets the ShellStream that async result was created for.
+        /// </summary>
+        /// <value>The ShellStream.</value>
+        internal ShellStream ShellStream { get; private set; }
+
+        ///// <summary>
+        ///// Gets or sets the bytes received. If SFTP only file bytes are counted.
+        ///// </summary>
+        ///// <value>Total bytes received.</value>
+        //public int BytesReceived { get; set; }
+
+        ///// <summary>
+        ///// Gets or sets the bytes sent by SFTP.
+        ///// </summary>
+        ///// <value>Total bytes sent.</value>
+        //public int BytesSent { get; set; }
+
+        #region IAsyncResult Members
+
+        /// <summary>
+        /// Gets a user-defined object that qualifies or contains information about an asynchronous operation.
+        /// </summary>
+        /// <returns>A user-defined object that qualifies or contains information about an asynchronous operation.</returns>
+        public object AsyncState { get; internal set; }
+
+        /// <summary>
+        /// Gets a <see cref="T:System.Threading.WaitHandle"/> that is used to wait for an asynchronous operation to complete.
+        /// </summary>
+        /// <returns>A <see cref="T:System.Threading.WaitHandle"/> that is used to wait for an asynchronous operation to complete.</returns>
+        public WaitHandle AsyncWaitHandle { get; internal set; }
+
+        /// <summary>
+        /// Gets a value that indicates whether the asynchronous operation completed synchronously.
+        /// </summary>
+        /// <returns>true if the asynchronous operation completed synchronously; otherwise, false.</returns>
+        public bool CompletedSynchronously { get; internal set; }
+
+        /// <summary>
+        /// Gets a value that indicates whether the asynchronous operation has completed.
+        /// </summary>
+        /// <returns>true if the operation is complete; otherwise, false.</returns>
+        public bool IsCompleted { get; internal set; }
+
+        #endregion
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="CommandAsyncResult" /> class.
+        /// </summary>
+        /// <param name="shellStream">The shell stream.</param>
+        internal ExpectAsyncResult(ShellStream shellStream)
+        {
+            this.ShellStream = shellStream;
+        }
+    }
+}

+ 0 - 3
Renci.SshClient/Renci.SshNet/PrivateKeyAuthenticationMethod.cs

@@ -55,9 +55,6 @@ namespace Renci.SshNet
         /// <returns></returns>
         public override AuthenticationResult Authenticate(Session session)
         {
-            if (this.KeyFiles == null)
-                return AuthenticationResult.Failure;
-
             session.UserAuthenticationSuccessReceived += Session_UserAuthenticationSuccessReceived;
             session.UserAuthenticationFailureReceived += Session_UserAuthenticationFailureReceived;
             session.MessageReceived += Session_MessageReceived;

+ 2 - 0
Renci.SshClient/Renci.SshNet/Renci.SshNet.csproj

@@ -131,6 +131,8 @@
       <SubType>Code</SubType>
     </Compile>
     <Compile Include="ConnectionInfo.cs" />
+    <Compile Include="ShellStream.NET40.cs" />
+    <Compile Include="ExpectAsyncResult.cs" />
     <Compile Include="Security\KeyExchangeDiffieHellmanGroupSha1.cs" />
     <Compile Include="SftpClient.NET.cs" />
     <Compile Include="KeyboardInteractiveAuthenticationMethod.cs">

+ 17 - 0
Renci.SshClient/Renci.SshNet/ShellStream.NET40.cs

@@ -0,0 +1,17 @@
+using System;
+using System.Threading.Tasks;
+
+namespace Renci.SshNet
+{
+    /// <summary>
+    /// Represents instance of the SSH shell object
+    /// </summary>
+    public partial class ShellStream
+    {
+        /// <exception cref="ArgumentNullException"><paramref name=" action"/> is null.</exception>
+        partial void ExecuteThread(Action action)
+        {
+            Task.Factory.StartNew(action);
+        }
+    }
+}

+ 250 - 53
Renci.SshClient/Renci.SshNet/ShellStream.cs

@@ -13,7 +13,7 @@ namespace Renci.SshNet
     /// <summary>
     /// Contains operation for working with SSH Shell.
     /// </summary>
-    public class ShellStream : Stream
+    public partial class ShellStream : Stream
     {
         private readonly Session _session;
 
@@ -27,6 +27,8 @@ namespace Renci.SshNet
 
         private Queue<byte> _outgoing;
 
+        private AutoResetEvent _dataReceived = new AutoResetEvent(false);
+
         /// <summary>
         /// Occurs when data was received.
         /// </summary>
@@ -260,12 +262,127 @@ namespace Renci.SshNet
             var expectedFound = false;
             var text = string.Empty;
 
-            lock (this._incoming)
+            do
+            {
+                byte[] textData = null;
+                lock (this._incoming)
+                {
+                    textData = this._incoming.ToArray();
+                }
+
+                if (textData.Length > 0)
+                    text = this._encoding.GetString(textData, 0, textData.Length);
+
+                if (text.Length > 0)
+                {
+                    foreach (var expectAction in expectActions)
+                    {
+                        var match = expectAction.Expect.Match(text);
+
+                        if (match.Success)
+                        {
+                            var result = text.Substring(0, match.Index + match.Length);
+
+                            lock (this._incoming)
+                            {
+                                for (int i = 0; i < match.Index + match.Length; i++)
+                                {
+                                    //  Remove processed items from the queue
+                                    this._incoming.Dequeue();
+                                }
+                            }
+
+                            expectAction.Action(result);
+                            expectedFound = true;
+                        }
+                    }
+                }
+
+                if (!expectedFound)
+                {
+                    if (timeout != null)
+                    {
+                        if (!this._dataReceived.WaitOne(timeout))
+                        {
+                            return;
+                        }
+                    }
+                    else
+                    {
+                        this._dataReceived.WaitOne();
+                    }
+                }
+            }
+            while (!expectedFound);
+        }
+
+        /// <summary>
+        /// Begins the expect.
+        /// </summary>
+        /// <param name="expectActions">The expect actions.</param>
+        /// <returns></returns>
+        public IAsyncResult BeginExpect(params ExpectAction[] expectActions)
+        {
+            return this.BeginExpect(TimeSpan.Zero, null, null, expectActions);
+        }
+
+        /// <summary>
+        /// Begins the expect.
+        /// </summary>
+        /// <param name="callback">The callback.</param>
+        /// <param name="state">The state.</param>
+        /// <param name="expectActions">The expect actions.</param>
+        /// <returns></returns>
+        public IAsyncResult BeginExpect(AsyncCallback callback, params ExpectAction[] expectActions)
+        {
+            return this.BeginExpect(TimeSpan.Zero, callback, null, expectActions);
+        }
+
+        /// <summary>
+        /// Begins the expect.
+        /// </summary>
+        /// <param name="callback">The callback.</param>
+        /// <param name="state">The state.</param>
+        /// <param name="expectActions">The expect actions.</param>
+        /// <returns></returns>
+        public IAsyncResult BeginExpect(AsyncCallback callback, object state, params ExpectAction[] expectActions)
+        {
+            return this.BeginExpect(TimeSpan.Zero, callback, state, expectActions);
+        }
+
+        /// <summary>
+        /// Begins the expect.
+        /// </summary>
+        /// <param name="timeout">The timeout.</param>
+        /// <param name="callback">The callback.</param>
+        /// <param name="state">The state.</param>
+        /// <param name="expectActions">The expect actions.</param>
+        /// <returns></returns>
+        public IAsyncResult BeginExpect(TimeSpan timeout, AsyncCallback callback, object state, params ExpectAction[] expectActions)
+        {
+            var text = string.Empty;
+
+            //  Create new AsyncResult object
+            var asyncResult = new ExpectAsyncResult(this)
+            {
+                AsyncWaitHandle = new ManualResetEvent(false),
+                IsCompleted = false,
+                AsyncState = state,
+            };
+
+            //  Execute callback on different thread                
+            this.ExecuteThread(() =>
             {
                 do
                 {
-                    if (this._incoming.Count > 0)
-                        text = this._encoding.GetString(this._incoming.ToArray(), 0, this._incoming.Count);
+                    byte[] textData = null;
+                    lock (this._incoming)
+                    {
+                        textData = this._incoming.ToArray();
+                    }
+
+                    if (textData.Length > 0)
+                        text = this._encoding.GetString(textData, 0, textData.Length);
 
                     if (text.Length > 0)
                     {
@@ -277,30 +394,65 @@ namespace Renci.SshNet
                             {
                                 var result = text.Substring(0, match.Index + match.Length);
 
-                                for (int i = 0; i < match.Index + match.Length; i++)
+                                lock (this._incoming)
                                 {
-                                    //  Remove processed items from the queue
-                                    this._incoming.Dequeue();
+                                    for (int i = 0; i < match.Index + match.Length; i++)
+                                    {
+                                        //  Remove processed items from the queue
+                                        this._incoming.Dequeue();
+                                    }
                                 }
 
                                 expectAction.Action(result);
-                                expectedFound = true;
+
+                                if (callback != null)
+                                {
+                                    callback(asyncResult);
+                                }
+                                ((EventWaitHandle)asyncResult.AsyncWaitHandle).Set();
+                                break;
                             }
                         }
                     }
-
-                    if (!expectedFound)
+                    if (timeout != null)
                     {
-                        if (timeout == TimeSpan.Zero)
+                        if (!this._dataReceived.WaitOne(timeout))
                         {
-                            Monitor.Wait(this._incoming);
+                            if (callback != null)
+                            {
+                                callback(asyncResult);
+                            }
+                            ((EventWaitHandle)asyncResult.AsyncWaitHandle).Set();
+                            break;
                         }
-                        else if (!Monitor.Wait(this._incoming, timeout))
-                            return;
                     }
-                }
-                while (!expectedFound);
+                    else
+                    {
+                        this._dataReceived.WaitOne();
+                    }
+                } while (true);
+            });
+
+            return asyncResult;
+        }
+
+        /// <summary>
+        /// Ends the execute.
+        /// </summary>
+        /// <param name="asyncResult">The async result.</param>
+        /// <exception cref="System.ArgumentException">Either the IAsyncResult object did not come from the corresponding async method on this type, or EndExecute was called multiple times with the same IAsyncResult.</exception>
+        public void EndExpect(IAsyncResult asyncResult)
+        {
+            var expectAsyncResult = asyncResult as ExpectAsyncResult;
+            if (expectAsyncResult != null && expectAsyncResult.ShellStream == this)
+            {
+                //  Make sure that operation completed if not wait for it to finish
+                this.WaitHandle(asyncResult.AsyncWaitHandle);
+
+                return;
             }
+
+            throw new ArgumentException("Either the IAsyncResult object did not come from the corresponding async method on this type, or EndExecute was called multiple times with the same IAsyncResult.");
         }
 
         /// <summary>
@@ -348,27 +500,43 @@ namespace Renci.SshNet
         {
             var text = string.Empty;
 
+            byte[] textData = null;
             lock (this._incoming)
             {
-                if (this._incoming.Count > 0)
-                    text = this._encoding.GetString(this._incoming.ToArray(), 0, this._incoming.Count);
+                textData = this._incoming.ToArray();
+            }
+
+            if (textData.Length > 0)
+                text = this._encoding.GetString(textData, 0, textData.Length);
 
-                var match = regex.Match(text.ToString());
-                while (!match.Success)
+            var match = regex.Match(text.ToString());
+            while (!match.Success)
+            {
+                if (timeout != null)
                 {
-                    if (timeout == TimeSpan.Zero)
+                    if (!this._dataReceived.WaitOne(timeout))
                     {
-                        Monitor.Wait(this._incoming);
-                    }
-                    else if (!Monitor.Wait(this._incoming, timeout))
                         return null;
+                    }
+                }
+                else
+                {
+                    this._dataReceived.WaitOne();
+                }
 
-                    if (this._incoming.Count > 0)
-                        text = this._encoding.GetString(this._incoming.ToArray(), 0, this._incoming.Count);
-
-                    match = regex.Match(text.ToString());
+                lock (this._incoming)
+                {
+                    textData = this._incoming.ToArray();
                 }
 
+                if (textData.Length > 0)
+                    text = this._encoding.GetString(textData, 0, textData.Length);
+
+                match = regex.Match(text.ToString());
+            }
+
+            lock (this._incoming)
+            {
                 for (int i = 0; i < match.Index + match.Length; i++)
                 {
                     //  Remove processed items from the queue
@@ -399,36 +567,54 @@ namespace Renci.SshNet
         {
             var text = string.Empty;
 
+            byte[] textData = null;
             lock (this._incoming)
             {
-                if (this._incoming.Count > 0)
-                    text = this._encoding.GetString(this._incoming.ToArray(), 0, this._incoming.Count);
+                textData = this._incoming.ToArray();
+            }
 
-                var index = text.ToString().IndexOf("\r\n");
-                while (index < 0)
+            if (textData.Length > 0)
+                text = this._encoding.GetString(textData, 0, textData.Length);
+
+
+            var index = text.ToString().IndexOf("\r\n");
+            while (index < 0)
+            {
+                if (timeout != null)
                 {
-                    if (timeout == TimeSpan.Zero)
+                    if (!this._dataReceived.WaitOne(timeout))
                     {
-                        Monitor.Wait(this._incoming);
-                    }
-                    else if (!Monitor.Wait(this._incoming, timeout))
                         return null;
+                    }
+                }
+                else
+                {
+                    this._dataReceived.WaitOne();
+                }
 
-                    if (this._incoming.Count > 0)
-                        text = this._encoding.GetString(this._incoming.ToArray(), 0, this._incoming.Count);
-
-                    index = text.ToString().IndexOf("\r\n");
+                lock (this._incoming)
+                {
+                    textData = this._incoming.ToArray();
                 }
 
-                text = text.Substring(0, index);
+                if (textData.Length > 0)
+                    text = this._encoding.GetString(textData, 0, textData.Length);
+
+
+                index = text.ToString().IndexOf("\r\n");
+            }
+
+            text = text.Substring(0, index);
 
+            //  Remove processed items from the queue
+            lock (this._incoming)
+            {
                 for (int i = 0; i < index + 2; i++)
                 {
-                    //  Remove processed items from the queue
                     this._incoming.Dequeue();
                 }
-
             }
+
             return text.ToString();
         }
 
@@ -440,13 +626,16 @@ namespace Renci.SshNet
         {
             var text = string.Empty;
 
+            byte[] textData = null;
             lock (this._incoming)
             {
-                if (this._incoming.Count > 0)
-                    text = this._encoding.GetString(this._incoming.ToArray(), 0, this._incoming.Count);
-
+                textData = this._incoming.ToArray();
                 this._incoming.Clear();
             }
+
+            if (textData.Length > 0)
+                text = this._encoding.GetString(textData, 0, textData.Length);
+
             return text.ToString();
         }
 
@@ -491,6 +680,17 @@ namespace Renci.SshNet
             }
         }
 
+        /// <summary>
+        /// Waits for the handle to be signaled or for an error to occurs.
+        /// </summary>
+        /// <param name="waitHandle">The wait handle.</param>
+        protected void WaitHandle(WaitHandle waitHandle)
+        {
+            this._session.WaitHandle(waitHandle);
+        }
+
+        partial void ExecuteThread(Action action);
+
         private void Session_ErrorOccured(object sender, ExceptionEventArgs e)
         {
             this.OnRaiseError(e);
@@ -515,16 +715,13 @@ namespace Renci.SshNet
 
         private void Channel_DataReceived(object sender, ChannelDataEventArgs e)
         {
-            lock (this._incoming)
+            foreach (var b in e.Data)
             {
-                foreach (var b in e.Data)
-                {
-                    _incoming.Enqueue(b);
-                }
-
-                Monitor.Pulse(this._incoming);
+                _incoming.Enqueue(b);
             }
 
+            this._dataReceived.Set();
+
             this.OnDataReceived(e.Data);
         }
 

+ 1 - 1
Renci.SshClient/Renci.SshNet/SshClient.cs

@@ -332,7 +332,7 @@ namespace Renci.SshNet
         /// <returns></returns>
         public ShellStream CreateShellStream(string terminalName, uint columns, uint rows, uint width, uint height, int bufferSize)
         {
-            return this.CreateShellStream(terminalName, columns, rows, width, height, bufferSize);
+            return this.CreateShellStream(terminalName, columns, rows, width, height, bufferSize, null);
         }
 
         /// <summary>