├── .gitignore
├── Cbonnell.DotNetExpect.Test
├── Cbonnell.DotNetExpect.Test.csproj
├── ChildProcessOptionsTest.cs
├── ChildProcessTest.cs
├── Properties
│ └── AssemblyInfo.cs
├── TestEnvironment.cs
└── TestUtilities.cs
├── Cbonnell.DotNetExpect
├── Cbonnell.DotNetExpect.csproj
├── Cbonnell.DotNetExpect.nuspec
├── Cbonnell.DotNetExpect.sln
├── ChildProcess.cs
├── ChildProcessOptions.cs
├── CommandResult.cs
├── ConsoleInterface.cs
├── OperationFailedException.cs
├── Properties
│ └── AssemblyInfo.cs
├── ProxyProcess.cs
└── ProxyProcessManager.cs
├── LICENSE
└── README.md
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 |
4 | # User-specific files
5 | *.suo
6 | *.user
7 | *.sln.docstates
8 |
9 | # Build results
10 | [Dd]ebug/
11 | [Dd]ebugPublic/
12 | [Rr]elease/
13 | [Rr]eleases/
14 | x64/
15 | x86/
16 | build/
17 | bld/
18 | [Bb]in/
19 | [Oo]bj/
20 |
21 | # Roslyn cache directories
22 | *.ide/
23 |
24 | # MSTest test Results
25 | [Tt]est[Rr]esult*/
26 | [Bb]uild[Ll]og.*
27 |
28 | #NUNIT
29 | *.VisualState.xml
30 | TestResult.xml
31 |
32 | # Build Results of an ATL Project
33 | [Dd]ebugPS/
34 | [Rr]eleasePS/
35 | dlldata.c
36 |
37 | *_i.c
38 | *_p.c
39 | *_i.h
40 | *.ilk
41 | *.meta
42 | *.obj
43 | *.pch
44 | *.pdb
45 | *.pgc
46 | *.pgd
47 | *.rsp
48 | *.sbr
49 | *.tlb
50 | *.tli
51 | *.tlh
52 | *.tmp
53 | *.tmp_proj
54 | *.log
55 | *.vspscc
56 | *.vssscc
57 | .builds
58 | *.pidb
59 | *.svclog
60 | *.scc
61 |
62 | # Chutzpah Test files
63 | _Chutzpah*
64 |
65 | # Visual C++ cache files
66 | ipch/
67 | *.aps
68 | *.ncb
69 | *.opensdf
70 | *.sdf
71 | *.cachefile
72 |
73 | # Visual Studio profiler
74 | *.psess
75 | *.vsp
76 | *.vspx
77 |
78 | # TFS 2012 Local Workspace
79 | $tf/
80 |
81 | # Guidance Automation Toolkit
82 | *.gpState
83 |
84 | # ReSharper is a .NET coding add-in
85 | _ReSharper*/
86 | *.[Rr]e[Ss]harper
87 | *.DotSettings.user
88 |
89 | # JustCode is a .NET coding addin-in
90 | .JustCode
91 |
92 | # TeamCity is a build add-in
93 | _TeamCity*
94 |
95 | # DotCover is a Code Coverage Tool
96 | *.dotCover
97 |
98 | # NCrunch
99 | _NCrunch_*
100 | .*crunch*.local.xml
101 |
102 | # MightyMoose
103 | *.mm.*
104 | AutoTest.Net/
105 |
106 | # Web workbench (sass)
107 | .sass-cache/
108 |
109 | # Installshield output folder
110 | [Ee]xpress/
111 |
112 | # DocProject is a documentation generator add-in
113 | DocProject/buildhelp/
114 | DocProject/Help/*.HxT
115 | DocProject/Help/*.HxC
116 | DocProject/Help/*.hhc
117 | DocProject/Help/*.hhk
118 | DocProject/Help/*.hhp
119 | DocProject/Help/Html2
120 | DocProject/Help/html
121 |
122 | # Click-Once directory
123 | publish/
124 |
125 | # Publish Web Output
126 | *.[Pp]ublish.xml
127 | *.azurePubxml
128 | # TODO: Comment the next line if you want to checkin your web deploy settings
129 | # but database connection strings (with potential passwords) will be unencrypted
130 | *.pubxml
131 | *.publishproj
132 |
133 | # NuGet Packages
134 | *.nupkg
135 | # The packages folder can be ignored because of Package Restore
136 | **/packages/*
137 | # except build/, which is used as an MSBuild target.
138 | !**/packages/build/
139 | # If using the old MSBuild-Integrated Package Restore, uncomment this:
140 | #!**/packages/repositories.config
141 |
142 | # Windows Azure Build Output
143 | csx/
144 | *.build.csdef
145 |
146 | # Windows Store app package directory
147 | AppPackages/
148 |
149 | # Others
150 | sql/
151 | *.Cache
152 | ClientBin/
153 | [Ss]tyle[Cc]op.*
154 | ~$*
155 | *~
156 | *.dbmdl
157 | *.dbproj.schemaview
158 | *.pfx
159 | *.publishsettings
160 | node_modules/
161 |
162 | # RIA/Silverlight projects
163 | Generated_Code/
164 |
165 | # Backup & report files from converting an old project file
166 | # to a newer Visual Studio version. Backup files are not needed,
167 | # because we have git ;-)
168 | _UpgradeReport_Files/
169 | Backup*/
170 | UpgradeLog*.XML
171 | UpgradeLog*.htm
172 |
173 | # SQL Server files
174 | *.mdf
175 | *.ldf
176 |
177 | # Business Intelligence projects
178 | *.rdl.data
179 | *.bim.layout
180 | *.bim_*.settings
181 |
182 | # Microsoft Fakes
183 | FakesAssemblies/
184 |
--------------------------------------------------------------------------------
/Cbonnell.DotNetExpect.Test/Cbonnell.DotNetExpect.Test.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Debug
6 | AnyCPU
7 | {71ED942A-A513-4B8B-8381-2050A7636652}
8 | Library
9 | Properties
10 | Cbonnell.DotNetExpect.Test
11 | Cbonnell.DotNetExpect.Test
12 | v3.5
13 | 512
14 | Client
15 |
16 |
17 | true
18 | full
19 | false
20 | bin\Debug\
21 | DEBUG;TRACE
22 | prompt
23 | 4
24 |
25 |
26 | pdbonly
27 | true
28 | bin\Release\
29 | TRACE
30 | prompt
31 | 4
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 | {0b25d3a5-eb12-42b6-887d-193bc0770d88}
52 | Cbonnell.DotNetExpect
53 |
54 |
55 |
56 |
63 |
--------------------------------------------------------------------------------
/Cbonnell.DotNetExpect.Test/ChildProcessOptionsTest.cs:
--------------------------------------------------------------------------------
1 | /*
2 | DotNetExpect
3 | Copyright (c) Corey Bonnell, All rights reserved.
4 |
5 | This library is free software; you can redistribute it and/or
6 | modify it under the terms of the GNU Lesser General Public
7 | License as published by the Free Software Foundation; either
8 | version 3.0 of the License, or (at your option) any later version.
9 |
10 | This library is distributed in the hope that it will be useful,
11 | but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 | Lesser General Public License for more details.
14 |
15 | You should have received a copy of the GNU Lesser General Public
16 | License along with this library. */
17 |
18 | using NUnit.Framework;
19 | using System;
20 | using System.Linq;
21 |
22 | namespace Cbonnell.DotNetExpect.Test
23 | {
24 | [TestFixture]
25 | public class ChildProcessOptionsTest
26 | {
27 | [SetUp]
28 | public void SetUp()
29 | {
30 | TestUtilities.EnsureProxyExit();
31 | }
32 |
33 | [Test]
34 | public void DefaultValuesExpected()
35 | {
36 | ChildProcessOptions options = new ChildProcessOptions();
37 | Assert.AreEqual(Environment.NewLine, options.NewLine);
38 | Assert.IsTrue(options.AttachConsole);
39 | Assert.AreEqual(10 * 1000, options.AttachConsoleTimeoutMilliseconds);
40 | Assert.AreEqual(60 * 1000, options.TimeoutMilliseconds);
41 | Assert.IsTrue(options.ClearConsole);
42 | }
43 |
44 | [Test]
45 | public void DefaultConsoleClearsAfterMatch()
46 | {
47 | using (ChildProcess childProc = new ChildProcess(TestEnvironment.CMD_EXE_NAME))
48 | {
49 | string content = childProc.Read(TestEnvironment.PROMPT_CHAR.ToString());
50 | childProc.WriteLine("echo \"hello world\"");
51 | content = childProc.Read(TestEnvironment.PROMPT_CHAR.ToString());
52 | Assert.AreEqual(1, content.Count((c) => c.Equals(TestEnvironment.PROMPT_CHAR)));
53 | }
54 | }
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/Cbonnell.DotNetExpect.Test/ChildProcessTest.cs:
--------------------------------------------------------------------------------
1 | /*
2 | DotNetExpect
3 | Copyright (c) Corey Bonnell, All rights reserved.
4 |
5 | This library is free software; you can redistribute it and/or
6 | modify it under the terms of the GNU Lesser General Public
7 | License as published by the Free Software Foundation; either
8 | version 3.0 of the License, or (at your option) any later version.
9 |
10 | This library is distributed in the hope that it will be useful,
11 | but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 | Lesser General Public License for more details.
14 |
15 | You should have received a copy of the GNU Lesser General Public
16 | License along with this library. */
17 |
18 | using NUnit.Framework;
19 | using System;
20 | using System.Diagnostics;
21 | using System.Text.RegularExpressions;
22 | using System.Threading;
23 |
24 | namespace Cbonnell.DotNetExpect.Test
25 | {
26 | [TestFixture]
27 | public class ChildProcessTest
28 | {
29 | [SetUp]
30 | public void SetUp()
31 | {
32 | TestUtilities.EnsureProxyExit();
33 | }
34 |
35 | [Test]
36 | [ExpectedException(typeof(ArgumentNullException))]
37 | public void ConstructorNullFilePath()
38 | {
39 | new ChildProcess(null);
40 | }
41 |
42 | [Test]
43 | [ExpectedException(typeof(ArgumentNullException))]
44 | public void ConstructorNullArguments()
45 | {
46 | new ChildProcess(TestEnvironment.DUMMY_EXE_NAME, null, new ChildProcessOptions());
47 | }
48 |
49 | [Test]
50 | [ExpectedException(typeof(ArgumentNullException))]
51 | public void ConstructorNullWorkingDirectory()
52 | {
53 | new ChildProcess(TestEnvironment.DUMMY_EXE_NAME, TestEnvironment.DUMMY_ARGUMENTS, null, new ChildProcessOptions());
54 | }
55 |
56 | [Test]
57 | [ExpectedException(typeof(ArgumentNullException))]
58 | public void ConstructorNullOptions()
59 | {
60 | new ChildProcess(TestEnvironment.DUMMY_EXE_NAME, TestEnvironment.DUMMY_ARGUMENTS, Environment.CurrentDirectory, null);
61 | }
62 |
63 | [Test]
64 | [ExpectedException(typeof(OperationFailedException))]
65 | public void ProcessNoExist()
66 | {
67 | using (ChildProcess childProc = new ChildProcess(Guid.NewGuid().ToString() + ".exe")) { }
68 | }
69 |
70 | [Test]
71 | [ExpectedException(typeof(ArgumentNullException))]
72 | public void ReadNullRegex()
73 | {
74 | using (ChildProcess childProc = new ChildProcess(TestEnvironment.CMD_EXE_NAME))
75 | {
76 | childProc.Match(null);
77 | }
78 | }
79 |
80 | [Test]
81 | [ExpectedException(typeof(ArgumentNullException))]
82 | public void WriteNullString()
83 | {
84 | using (ChildProcess childProc = new ChildProcess(TestEnvironment.CMD_EXE_NAME))
85 | {
86 | childProc.WriteLine(null);
87 | }
88 | }
89 |
90 | [Test]
91 | public void SimpleReadUntilPrompt()
92 | {
93 | using (ChildProcess childProc = new ChildProcess(TestEnvironment.CMD_EXE_NAME))
94 | {
95 | string content = childProc.Read(TestEnvironment.PROMPT_CHAR.ToString());
96 | Console.WriteLine(content);
97 | Assert.IsTrue(content.EndsWith(TestEnvironment.PROMPT_CHAR.ToString()));
98 | }
99 | }
100 |
101 | [Test]
102 | public void SimpleMatch()
103 | {
104 | using (ChildProcess childProc = new ChildProcess(TestEnvironment.CMD_EXE_NAME))
105 | {
106 | childProc.WriteLine("dir");
107 | Match m = childProc.Match(new Regex(@"Volume Serial Number is (?[0-9A-F]{4}-[0-9A-F]{4})"));
108 | Console.WriteLine("Primary volume serial number: {0}", m.Groups["VolumeSerial"].Value);
109 | Assert.IsTrue(m.Success);
110 | }
111 | }
112 |
113 | [Test]
114 | public void SimpleRead()
115 | {
116 | using (ChildProcess childProc = new ChildProcess(TestEnvironment.CMD_EXE_NAME))
117 | {
118 | string content = childProc.Read(Environment.CurrentDirectory + ">");
119 | Assert.IsTrue(content.Contains(Environment.CurrentDirectory + ">"));
120 | }
121 | }
122 |
123 | [Test]
124 | [ExpectedException(typeof(TimeoutException))]
125 | public void ReadTimeout()
126 | {
127 | using (ChildProcess childProc = new ChildProcess(TestEnvironment.CMD_EXE_NAME, new ChildProcessOptions() { TimeoutMilliseconds = 0 }))
128 | {
129 | childProc.Read(Guid.NewGuid().ToString());
130 | }
131 | }
132 |
133 | [Test]
134 | [ExpectedException(typeof(TimeoutException))]
135 | public void MatchTimeout()
136 | {
137 | using (ChildProcess childProc = new ChildProcess(TestEnvironment.CMD_EXE_NAME, new ChildProcessOptions() { TimeoutMilliseconds = 0 }))
138 | {
139 | childProc.Match(new Regex(Guid.NewGuid().ToString()));
140 | }
141 | }
142 |
143 | [Test]
144 | [ExpectedException(typeof(ObjectDisposedException))]
145 | public void ObjectDisposed()
146 | {
147 | ChildProcess childProc;
148 | using (childProc = new ChildProcess(TestEnvironment.CMD_EXE_NAME)) { }
149 | childProc.ClearConsole();
150 | }
151 |
152 | [Test]
153 | public void VerifyChildDeadOnDispose()
154 | {
155 | Process p = null;
156 | try
157 | {
158 | using (ChildProcess childProc = new ChildProcess(TestEnvironment.CMD_EXE_NAME))
159 | {
160 | p = Process.GetProcessById(childProc.ChildProcessId);
161 | }
162 | TestUtilities.WaitForProcessExitAndThrow(p);
163 | }
164 | finally
165 | {
166 | if (p != null)
167 | {
168 | p.Dispose();
169 | }
170 | }
171 | }
172 |
173 | [Test]
174 | public void VerifyProxyDeadOnDispose()
175 | {
176 | Process[] powerShells = null;
177 |
178 | try
179 | {
180 | using (ChildProcess childProc = new ChildProcess(TestEnvironment.CMD_EXE_NAME))
181 | {
182 | powerShells = Process.GetProcessesByName(TestEnvironment.PROXY_PROCESS_NAME);
183 | Assert.AreEqual(1, powerShells.Length);
184 | }
185 | TestUtilities.WaitForProcessExitAndThrow(powerShells[0]);
186 | }
187 | finally
188 | {
189 | if (powerShells != null)
190 | {
191 | Array.ForEach(powerShells, (p) => p.Dispose());
192 | }
193 | }
194 | }
195 |
196 | [Test]
197 | public void TestExistingProcess()
198 | {
199 | using (Process p = new Process())
200 | {
201 | try
202 | {
203 | p.StartInfo.FileName = TestEnvironment.CMD_EXE_NAME;
204 | p.StartInfo.UseShellExecute = false;
205 | p.StartInfo.CreateNoWindow = true;
206 | p.Start();
207 | using (ChildProcess childProc = new ChildProcess(p.Id))
208 | {
209 | string prompt = childProc.Read(TestEnvironment.PROMPT_CHAR.ToString());
210 | Console.WriteLine(prompt);
211 | }
212 | }
213 | finally
214 | {
215 | p.Kill();
216 | }
217 | }
218 | }
219 |
220 | [Test]
221 | public void TestExistingProcessStillAliveAfterDispose()
222 | {
223 | using (Process p = new Process())
224 | {
225 | try
226 | {
227 | p.StartInfo.FileName = TestEnvironment.CMD_EXE_NAME;
228 | p.StartInfo.UseShellExecute = false;
229 | p.StartInfo.CreateNoWindow = true;
230 | p.Start();
231 | using (ChildProcess childProc = new ChildProcess(p.Id)) { }
232 |
233 | Assert.IsFalse(p.HasExited);
234 | }
235 | finally
236 | {
237 | p.Kill();
238 | }
239 | }
240 | }
241 | }
242 | }
243 |
--------------------------------------------------------------------------------
/Cbonnell.DotNetExpect.Test/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.CompilerServices;
3 | using System.Runtime.InteropServices;
4 |
5 | // General Information about an assembly is controlled through the following
6 | // set of attributes. Change these attribute values to modify the information
7 | // associated with an assembly.
8 | [assembly: AssemblyTitle("Cbonnell.DotNetExpect.Test")]
9 | [assembly: AssemblyDescription("Tests for Cbonnell.DotNetExpect")]
10 | [assembly: AssemblyConfiguration("")]
11 | [assembly: AssemblyCompany("Cbonnell")]
12 | [assembly: AssemblyProduct("Cbonnell.DotNetExpect.Test")]
13 | [assembly: AssemblyCopyright("Copyright © Corey Bonnell 2015")]
14 | [assembly: AssemblyTrademark("")]
15 | [assembly: AssemblyCulture("")]
16 |
17 | // Setting ComVisible to false makes the types in this assembly not visible
18 | // to COM components. If you need to access a type in this assembly from
19 | // COM, set the ComVisible attribute to true on that type.
20 | [assembly: ComVisible(false)]
21 |
22 | // The following GUID is for the ID of the typelib if this project is exposed to COM
23 | [assembly: Guid("0dc8eed2-c1eb-480e-93f0-e9fa807f4cad")]
24 |
25 | // Version information for an assembly consists of the following four values:
26 | //
27 | // Major Version
28 | // Minor Version
29 | // Build Number
30 | // Revision
31 | //
32 | // You can specify all the values or you can default the Build and Revision Numbers
33 | // by using the '*' as shown below:
34 | // [assembly: AssemblyVersion("1.0.*")]
35 | [assembly: AssemblyVersion("1.0.0.0")]
36 | [assembly: AssemblyFileVersion("1.0.0.0")]
37 |
--------------------------------------------------------------------------------
/Cbonnell.DotNetExpect.Test/TestEnvironment.cs:
--------------------------------------------------------------------------------
1 | /*
2 | DotNetExpect
3 | Copyright (c) Corey Bonnell, All rights reserved.
4 |
5 | This library is free software; you can redistribute it and/or
6 | modify it under the terms of the GNU Lesser General Public
7 | License as published by the Free Software Foundation; either
8 | version 3.0 of the License, or (at your option) any later version.
9 |
10 | This library is distributed in the hope that it will be useful,
11 | but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 | Lesser General Public License for more details.
14 |
15 | You should have received a copy of the GNU Lesser General Public
16 | License along with this library. */
17 |
18 | using System.Text.RegularExpressions;
19 |
20 | namespace Cbonnell.DotNetExpect.Test
21 | {
22 | internal static class TestEnvironment
23 | {
24 | public const string CMD_EXE_NAME = "cmd.exe";
25 | public const string DUMMY_EXE_NAME = "myprocess.exe";
26 | public const string DUMMY_ARGUMENTS = "foo hoge";
27 |
28 | public const string PROXY_PROCESS_NAME = "powershell";
29 |
30 | public const char PROMPT_CHAR = '>';
31 |
32 | public static readonly Regex CMD_PROMPT_REGEX = new Regex(TestEnvironment.PROMPT_CHAR.ToString());
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/Cbonnell.DotNetExpect.Test/TestUtilities.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Diagnostics;
4 | using System.Linq;
5 | using System.Text;
6 |
7 | namespace Cbonnell.DotNetExpect.Test
8 | {
9 | internal static class TestUtilities
10 | {
11 | public const int PROCESS_EXIT_TIMEOUT_MS = 10 * 1000;
12 |
13 | // Wait for any proxies to exit before running each test
14 | public static void EnsureProxyExit()
15 | {
16 | Process[] powerShells = Process.GetProcessesByName(TestEnvironment.PROXY_PROCESS_NAME);
17 | try
18 | {
19 | foreach (Process p in powerShells)
20 | {
21 | TestUtilities.WaitForProcessExitAndThrow(p);
22 | }
23 | }
24 | finally
25 | {
26 | Array.ForEach(powerShells, (p) => p.Dispose());
27 | }
28 | }
29 |
30 | public static void WaitForProcessExitAndThrow(Process p)
31 | {
32 | if (!p.WaitForExit(TestUtilities.PROCESS_EXIT_TIMEOUT_MS))
33 | {
34 | throw new TimeoutException();
35 | }
36 | }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/Cbonnell.DotNetExpect/Cbonnell.DotNetExpect.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Debug
6 | AnyCPU
7 | {0B25D3A5-EB12-42B6-887D-193BC0770D88}
8 | Library
9 | Properties
10 | Cbonnell.DotNetExpect
11 | Cbonnell.DotNetExpect
12 | v3.5
13 | 512
14 | Client
15 |
16 |
17 | true
18 | full
19 | false
20 | bin\Debug\
21 | DEBUG;TRACE
22 | prompt
23 | 4
24 | bin\Debug\Cbonnell.DotNetExpect.XML
25 |
26 |
27 | pdbonly
28 | true
29 | bin\Release\
30 | TRACE
31 | prompt
32 | 4
33 | bin\Release\Cbonnell.DotNetExpect.XML
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
61 |
--------------------------------------------------------------------------------
/Cbonnell.DotNetExpect/Cbonnell.DotNetExpect.nuspec:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | $id$
5 | $version$
6 | $title$
7 | $author$
8 | $author$
9 | https://raw.githubusercontent.com/CBonnell/dotnetexpect/master/LICENSE
10 | https://github.com/cbonnell/dotnetexpect
11 | false
12 | $description$
13 | Copyright 2015 $author$
14 | Expect Console Automation
15 |
16 |
17 |
--------------------------------------------------------------------------------
/Cbonnell.DotNetExpect/Cbonnell.DotNetExpect.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio 2013
4 | VisualStudioVersion = 12.0.30501.0
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Cbonnell.DotNetExpect", "Cbonnell.DotNetExpect.csproj", "{0B25D3A5-EB12-42B6-887D-193BC0770D88}"
7 | EndProject
8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Cbonnell.DotNetExpect.Test", "..\Cbonnell.DotNetExpect.Test\Cbonnell.DotNetExpect.Test.csproj", "{71ED942A-A513-4B8B-8381-2050A7636652}"
9 | EndProject
10 | Global
11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
12 | Debug|Any CPU = Debug|Any CPU
13 | Release|Any CPU = Release|Any CPU
14 | EndGlobalSection
15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
16 | {0B25D3A5-EB12-42B6-887D-193BC0770D88}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
17 | {0B25D3A5-EB12-42B6-887D-193BC0770D88}.Debug|Any CPU.Build.0 = Debug|Any CPU
18 | {0B25D3A5-EB12-42B6-887D-193BC0770D88}.Release|Any CPU.ActiveCfg = Release|Any CPU
19 | {0B25D3A5-EB12-42B6-887D-193BC0770D88}.Release|Any CPU.Build.0 = Release|Any CPU
20 | {71ED942A-A513-4B8B-8381-2050A7636652}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
21 | {71ED942A-A513-4B8B-8381-2050A7636652}.Debug|Any CPU.Build.0 = Debug|Any CPU
22 | {71ED942A-A513-4B8B-8381-2050A7636652}.Release|Any CPU.ActiveCfg = Release|Any CPU
23 | {71ED942A-A513-4B8B-8381-2050A7636652}.Release|Any CPU.Build.0 = Release|Any CPU
24 | EndGlobalSection
25 | GlobalSection(SolutionProperties) = preSolution
26 | HideSolutionNode = FALSE
27 | EndGlobalSection
28 | EndGlobal
29 |
--------------------------------------------------------------------------------
/Cbonnell.DotNetExpect/ChildProcess.cs:
--------------------------------------------------------------------------------
1 | /*
2 | DotNetExpect
3 | Copyright (c) Corey Bonnell, All rights reserved.
4 |
5 | This library is free software; you can redistribute it and/or
6 | modify it under the terms of the GNU Lesser General Public
7 | License as published by the Free Software Foundation; either
8 | version 3.0 of the License, or (at your option) any later version.
9 |
10 | This library is distributed in the hope that it will be useful,
11 | but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 | Lesser General Public License for more details.
14 |
15 | You should have received a copy of the GNU Lesser General Public
16 | License along with this library. */
17 |
18 | using System;
19 | using System.Collections.Generic;
20 | using System.Linq;
21 | using System.Text;
22 | using System.Text.RegularExpressions;
23 |
24 | namespace Cbonnell.DotNetExpect
25 | {
26 | ///
27 | /// Represents a child process whose console input and output can be accessed.
28 | ///
29 | public class ChildProcess : IDisposable
30 | {
31 | private ProxyProcessManager proxy = new ProxyProcessManager();
32 |
33 | ///
34 | /// Creates a new instance of .
35 | ///
36 | /// The path to the child process. The semantics in terms of how relative paths are handled are the same as with set to false.
37 | public ChildProcess(string filePath) : this(filePath, String.Empty) { }
38 |
39 | ///
40 | /// Creates a new instance of .
41 | ///
42 | /// The path to the child process. The semantics in terms of how relative paths are handled are the same as with set to false.
43 | /// The to use when accessing the console input and output of the child process.
44 | public ChildProcess(string filePath, ChildProcessOptions options) : this(filePath, String.Empty, options) { }
45 |
46 | ///
47 | /// Creates a new instance of .
48 | ///
49 | /// The path to the child process. The semantics in terms of how relative paths are handled are the same as with set to false.
50 | /// The command line arguments for the child process.
51 | public ChildProcess(string filePath, string arguments) : this(filePath, arguments, Environment.CurrentDirectory) { }
52 |
53 | ///
54 | /// Creates a new instance of .
55 | ///
56 | /// The path to the child process. The semantics in terms of how relative paths are handled are the same as with set to false.
57 | /// The command line arguments for the child process.
58 | /// The to use when accessing the console input and output of the child process.
59 | public ChildProcess(string filePath, string arguments, ChildProcessOptions options) : this(filePath, arguments, Environment.CurrentDirectory, options) { }
60 |
61 | ///
62 | /// Creates a new instance of .
63 | ///
64 | /// The path to the child process. The semantics in terms of how relative paths are handled are the same as with set to false.
65 | /// The command line arguments for the child process.
66 | /// The working directory for the child process.
67 | public ChildProcess(string filePath, string arguments, string workingDirectory) : this(filePath, arguments, workingDirectory, new ChildProcessOptions()) { }
68 |
69 | ///
70 | /// Creates a new instance of .
71 | ///
72 | /// The path to the child process. The semantics in terms of how relative paths are handled are the same as with set to false.
73 | /// The command line arguments for the child process.
74 | /// The working directory for the child process.
75 | /// The to use when accessing the console input and output of the child process.
76 | public ChildProcess(string filePath, string arguments, string workingDirectory, ChildProcessOptions options)
77 | {
78 | if (filePath == null)
79 | {
80 | throw new ArgumentNullException("filePath");
81 | }
82 | if (arguments == null)
83 | {
84 | throw new ArgumentNullException("arguments");
85 | }
86 | if (workingDirectory == null)
87 | {
88 | throw new ArgumentNullException("workingDirectory");
89 | }
90 | if (options == null)
91 | {
92 | throw new ArgumentNullException("options");
93 | }
94 |
95 | this.Options = options;
96 |
97 | try
98 | {
99 | this.proxy.Start();
100 | this.proxy.CommandPipeWriter.Write((byte)ProxyCommand.StartProcess);
101 | this.proxy.CommandPipeWriter.Write(filePath);
102 | this.proxy.CommandPipeWriter.Write(arguments);
103 | this.proxy.CommandPipeWriter.Write(workingDirectory);
104 | this.proxy.CommandPipeWriter.Flush();
105 |
106 | this.readResponseAndThrow();
107 | }
108 | catch (Exception)
109 | {
110 | this.Dispose();
111 | throw;
112 | }
113 | }
114 |
115 | ///
116 | /// Creates a new instance of .
117 | ///
118 | /// The process ID of the console process.
119 | /// This constructor is used to manipulate console I/O for a process that is already running.
120 | public ChildProcess(int pid) : this(pid, new ChildProcessOptions()) { }
121 |
122 | ///
123 | /// Creates a new instance of .
124 | ///
125 | /// The process ID of the console process.
126 | /// The to use when accessing the console input and output of the child process.
127 | /// This constructor is used to manipulate console I/O for a process that is already running.
128 | public ChildProcess(int pid, ChildProcessOptions options)
129 | {
130 | if (options == null)
131 | {
132 | throw new ArgumentNullException("options");
133 | }
134 |
135 | this.Options = options;
136 |
137 | try
138 | {
139 | this.proxy.Start();
140 | this.proxy.CommandPipeWriter.Write((byte)ProxyCommand.SetPid);
141 | this.proxy.CommandPipeWriter.Write(pid);
142 | this.proxy.CommandPipeWriter.Flush();
143 |
144 | this.readResponseAndThrow();
145 | }
146 | catch (Exception)
147 | {
148 | this.Dispose();
149 | throw;
150 | }
151 | }
152 |
153 | ///
154 | /// Reads the child process's console output (screen buffer) until the content of the screen output matches the specified .
155 | ///
156 | /// The regular expression to match the console screen output against.
157 | /// A that was returned after matching the console output with the specified .
158 | public Match Match(Regex regex)
159 | {
160 | if (regex == null)
161 | {
162 | throw new ArgumentNullException("regex");
163 | }
164 |
165 | this.checkDisposedAndThrow();
166 |
167 | return this.readLoopWithTimeout((s) => regex.Match(s), (m) => m.Success, this.Options.TimeoutMilliseconds);
168 | }
169 |
170 | ///
171 | /// Reads the child process's console output (screen buffer) until the content of the screen output contains the expected data.
172 | ///
173 | /// The string for which to search.
174 | /// The console output.
175 | public string Read(string expectedData)
176 | {
177 | if (expectedData == null)
178 | {
179 | throw new ArgumentNullException("expectedData");
180 | }
181 |
182 | this.checkDisposedAndThrow();
183 |
184 | return this.readLoopWithTimeout((s) => s, (s) => s.Contains(expectedData), this.Options.TimeoutMilliseconds);
185 | }
186 |
187 | ///
188 | /// Writes the specified string to the child process's console.
189 | ///
190 | /// The string data to write to the child process's console.
191 | public void Write(string data)
192 | {
193 | if (data == null)
194 | {
195 | throw new ArgumentNullException("data");
196 | }
197 |
198 | this.checkDisposedAndThrow();
199 |
200 | if (this.Options.AttachConsole)
201 | {
202 | this.attachConsole();
203 | }
204 |
205 | this.proxy.CommandPipeWriter.Write((byte)ProxyCommand.WriteConsole);
206 | this.proxy.CommandPipeWriter.Write(data);
207 | this.proxy.CommandPipeWriter.Flush();
208 | this.readResponseAndThrow();
209 | }
210 |
211 | ///
212 | /// Writes the specified string to the child process's console, first appending the value of to the string.
213 | ///
214 | /// The string data to write to the child process's console.
215 | public void WriteLine(string data)
216 | {
217 | if (data == null)
218 | {
219 | throw new ArgumentNullException("data");
220 | }
221 |
222 | data += this.Options.NewLine;
223 |
224 | this.Write(data);
225 | }
226 |
227 | ///
228 | /// Kills the child process.
229 | ///
230 | /// The exit code of the killed child process.
231 | /// After this method is called, this object is disposed no further operations can be performed on this object.
232 | public int Kill()
233 | {
234 | this.checkDisposedAndThrow();
235 |
236 | this.proxy.CommandPipeWriter.Write((byte)ProxyCommand.KillProcess);
237 | this.proxy.CommandPipeWriter.Flush();
238 | this.readResponseAndThrow();
239 | int exitCode = this.proxy.CommandPipeReader.ReadInt32(); // read the exit code
240 |
241 | this.Dispose();
242 |
243 | return exitCode;
244 | }
245 |
246 | ///
247 | /// Clears the child process's console output.
248 | ///
249 | public void ClearConsole()
250 | {
251 | this.checkDisposedAndThrow();
252 |
253 | this.proxy.CommandPipeWriter.Write((byte)ProxyCommand.ClearConsole);
254 | this.proxy.CommandPipeWriter.Flush();
255 | this.readResponseAndThrow();
256 | }
257 |
258 | ///
259 | /// Retrieves the process ID of the spawned child process.
260 | ///
261 | public int ChildProcessId
262 | {
263 | get
264 | {
265 | this.checkDisposedAndThrow();
266 |
267 | this.proxy.CommandPipeWriter.Write((byte)ProxyCommand.GetPid);
268 | this.proxy.CommandPipeWriter.Flush();
269 | this.readResponseAndThrow();
270 |
271 | return this.proxy.CommandPipeReader.ReadInt32();
272 | }
273 | }
274 |
275 | ///
276 | /// Retrieves the exit code of a child process that has exited (stopped executing).
277 | ///
278 | public int ChildExitCode
279 | {
280 | get
281 | {
282 | this.checkDisposedAndThrow();
283 |
284 | this.proxy.CommandPipeWriter.Write((byte)ProxyCommand.GetProcessExitCode);
285 | this.proxy.CommandPipeWriter.Flush();
286 | this.readResponseAndThrow();
287 |
288 | return this.proxy.CommandPipeReader.ReadInt32();
289 | }
290 | }
291 |
292 | ///
293 | /// Retrieves whether or not a child process has exited (stopped executing).
294 | ///
295 | public bool HasChildExited
296 | {
297 | get
298 | {
299 | this.checkDisposedAndThrow();
300 |
301 | this.proxy.CommandPipeWriter.Write((byte)ProxyCommand.GetHasProcessExited);
302 | this.proxy.CommandPipeWriter.Flush();
303 | this.readResponseAndThrow();
304 |
305 | return this.proxy.CommandPipeReader.ReadBoolean();
306 | }
307 | }
308 |
309 | ///
310 | /// Retrieves the used by the current instance of .
311 | ///
312 | public ChildProcessOptions Options
313 | {
314 | get;
315 | private set;
316 | }
317 |
318 | ///
319 | /// Disposes the current instance of .
320 | ///
321 | public void Dispose()
322 | {
323 | if (this.proxy != null)
324 | {
325 | this.proxy.Dispose();
326 | this.proxy = null;
327 | }
328 | }
329 |
330 | private void checkDisposedAndThrow()
331 | {
332 | if (this.proxy == null)
333 | {
334 | throw new ObjectDisposedException(this.GetType().FullName);
335 | }
336 | }
337 |
338 | private TReturn readLoopWithTimeout(Converter string2TypeConverter, Predicate isCompleteDelegate, int timeoutMilliseconds)
339 | {
340 | TimeSpan timeoutSpan = timeoutMilliseconds >= 0 ? TimeSpan.FromMilliseconds(timeoutMilliseconds) : default(TimeSpan);
341 |
342 | DateTime startTime = DateTime.Now;
343 | TReturn returnValue = default(TReturn);
344 | while (timeoutMilliseconds < 0 || DateTime.Now < startTime + timeoutSpan)
345 | {
346 | string output = this.readConsoleOutput();
347 | returnValue = string2TypeConverter.Invoke(output);
348 | if (isCompleteDelegate.Invoke(returnValue))
349 | {
350 | break;
351 | }
352 |
353 | // if we're here, then default the return value and loop back because it doesn't satisfy our condition
354 | returnValue = default(TReturn);
355 | }
356 |
357 | // if we're here then we've either satisified the condition or we timed out
358 | // compare the return value to the default for the return type to determine if we timed out or not
359 | if (Object.Equals(returnValue, default(TReturn)))
360 | {
361 | throw new TimeoutException();
362 | }
363 |
364 | if (this.Options.ClearConsole)
365 | {
366 | this.ClearConsole();
367 | }
368 |
369 | return returnValue;
370 | }
371 |
372 | private string readConsoleOutput()
373 | {
374 | if (this.Options.AttachConsole)
375 | {
376 | this.attachConsole();
377 | }
378 |
379 | this.proxy.CommandPipeWriter.Write((byte)ProxyCommand.ReadConsole);
380 | this.proxy.CommandPipeWriter.Flush();
381 | this.readResponseAndThrow();
382 |
383 | return this.proxy.CommandPipeReader.ReadString();
384 | }
385 |
386 | private void attachConsole()
387 | {
388 | this.proxy.CommandPipeWriter.Write((byte)ProxyCommand.AttachConsole);
389 | this.proxy.CommandPipeWriter.Write(this.Options.AttachConsoleTimeoutMilliseconds);
390 | this.proxy.CommandPipeWriter.Flush();
391 | this.readResponseAndThrow();
392 | }
393 |
394 | private void readResponseAndThrow()
395 | {
396 | CommandResult result = (CommandResult)this.proxy.CommandPipeReader.ReadByte();
397 | if (result != CommandResult.Success)
398 | {
399 | throw new OperationFailedException(result);
400 | }
401 | }
402 | }
403 | }
404 |
--------------------------------------------------------------------------------
/Cbonnell.DotNetExpect/ChildProcessOptions.cs:
--------------------------------------------------------------------------------
1 | /*
2 | DotNetExpect
3 | Copyright (c) Corey Bonnell, All rights reserved.
4 |
5 | This library is free software; you can redistribute it and/or
6 | modify it under the terms of the GNU Lesser General Public
7 | License as published by the Free Software Foundation; either
8 | version 3.0 of the License, or (at your option) any later version.
9 |
10 | This library is distributed in the hope that it will be useful,
11 | but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 | Lesser General Public License for more details.
14 |
15 | You should have received a copy of the GNU Lesser General Public
16 | License along with this library. */
17 |
18 | using System;
19 |
20 | namespace Cbonnell.DotNetExpect
21 | {
22 | ///
23 | /// Contains the various options for interacting with a .
24 | ///
25 | public class ChildProcessOptions
26 | {
27 | private const int MSEC_PER_SEC = 1000;
28 |
29 | ///
30 | /// Instantiates a new instance of .
31 | ///
32 | public ChildProcessOptions()
33 | {
34 | this.AttachConsole = true;
35 | this.NewLine = Environment.NewLine;
36 | this.AttachConsoleTimeoutMilliseconds = 10 * ChildProcessOptions.MSEC_PER_SEC;
37 | this.TimeoutMilliseconds = 60 * ChildProcessOptions.MSEC_PER_SEC;
38 | this.ClearConsole = true;
39 | }
40 |
41 | ///
42 | /// Whether or not to attach to the child process console on each input/output operation. This is sometimes required, as many command line applications create their own console.
43 | ///
44 | /// The default value is true.
45 | public bool AttachConsole
46 | {
47 | get;
48 | set;
49 | }
50 |
51 | private string newLine;
52 | ///
53 | /// The string to append to each specified string when calling . This is generally useful so that one does not need to manually add newline/carriage return characters to each string to be written.
54 | ///
55 | /// The default value is .
56 | public string NewLine
57 | {
58 | get
59 | {
60 | return this.newLine;
61 | }
62 | set
63 | {
64 | if (value == null)
65 | {
66 | throw new ArgumentNullException("value");
67 | }
68 | this.newLine = value;
69 | }
70 | }
71 |
72 | private int attachConsoleTimeoutMilliseconds;
73 | ///
74 | /// The amount of time to attempt to attach to a child process's console. This value may need to be adjusted if the child process takes a considerable amount of time to initialize before allocating its own console.
75 | ///
76 | /// The default value is 10,000 millseconds (10 seconds).
77 | public int AttachConsoleTimeoutMilliseconds
78 | {
79 | get
80 | {
81 | return this.attachConsoleTimeoutMilliseconds;
82 | }
83 | set
84 | {
85 | if (value < 0)
86 | {
87 | throw new ArgumentOutOfRangeException("value");
88 | }
89 | this.attachConsoleTimeoutMilliseconds = value;
90 | }
91 | }
92 |
93 | private int timeoutMilliseconds;
94 | ///
95 | /// The amount of time to wait for matching input when calling or before a is thrown.
96 | ///
97 | /// The default value is 60,000 milliseconds (60 seconds, or 1 minute). A negative value denotes that there is no timeout (will wait forever for matching input).
98 | public int TimeoutMilliseconds
99 | {
100 | get
101 | {
102 | return this.timeoutMilliseconds;
103 | }
104 | set
105 | {
106 | this.timeoutMilliseconds = value;
107 | }
108 | }
109 |
110 | ///
111 | /// Whether or not to clear the console of all content after successfully reading console content. This is useful so that previously read content is not returned on subsequent Reads.
112 | ///
113 | /// The default value is true.
114 | public bool ClearConsole
115 | {
116 | get;
117 | set;
118 | }
119 | }
120 | }
121 |
--------------------------------------------------------------------------------
/Cbonnell.DotNetExpect/CommandResult.cs:
--------------------------------------------------------------------------------
1 | /*
2 | DotNetExpect
3 | Copyright (c) Corey Bonnell, All rights reserved.
4 |
5 | This library is free software; you can redistribute it and/or
6 | modify it under the terms of the GNU Lesser General Public
7 | License as published by the Free Software Foundation; either
8 | version 3.0 of the License, or (at your option) any later version.
9 |
10 | This library is distributed in the hope that it will be useful,
11 | but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 | Lesser General Public License for more details.
14 |
15 | You should have received a copy of the GNU Lesser General Public
16 | License along with this library. */
17 |
18 |
19 | namespace Cbonnell.DotNetExpect
20 | {
21 | ///
22 | /// Represents the possible results that can be returned from a command invoked on a child process.
23 | ///
24 | public enum CommandResult
25 | {
26 | ///
27 | /// The command succeeded.
28 | ///
29 | Success = 0,
30 | ///
31 | /// A non-specific error occurred.
32 | ///
33 | GeneralFailure,
34 | ///
35 | /// An error occurred when attempting to spawn the child process.
36 | ///
37 | /// Generally this error is returned when the specified application does not exist.
38 | CouldNotSpawnChild,
39 | ///
40 | /// The the child process was already spawned for the current instance of .
41 | ///
42 | ChildAlreadySpawned,
43 | ///
44 | /// A call to retrieve the child process's exit code was attempted while the child process was still executing.
45 | ///
46 | ChildHasNotExited,
47 | ///
48 | /// The child process has not yet been spawned for the current instance .
49 | ///
50 | ChildNotSpawned,
51 | ///
52 | /// A process with the specified PID does not exist.
53 | ///
54 | ProcessDoesNotExist,
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/Cbonnell.DotNetExpect/ConsoleInterface.cs:
--------------------------------------------------------------------------------
1 | /*
2 | DotNetExpect
3 | Copyright (c) Corey Bonnell, All rights reserved.
4 |
5 | This library is free software; you can redistribute it and/or
6 | modify it under the terms of the GNU Lesser General Public
7 | License as published by the Free Software Foundation; either
8 | version 3.0 of the License, or (at your option) any later version.
9 |
10 | This library is distributed in the hope that it will be useful,
11 | but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 | Lesser General Public License for more details.
14 |
15 | You should have received a copy of the GNU Lesser General Public
16 | License along with this library. */
17 |
18 | using Microsoft.Win32.SafeHandles;
19 | using System;
20 | using System.ComponentModel;
21 | using System.Runtime.InteropServices;
22 | using System.Text;
23 |
24 | namespace Cbonnell.DotNetExpect
25 | {
26 | internal static class ConsoleInterface
27 | {
28 | private const char SPACE_CHAR = ' ';
29 |
30 | #region Public Interface
31 |
32 | public static string ReadConsole()
33 | {
34 | char[,] output = ConsoleInterface.ReadConsoleRaw();
35 | StringBuilder buffer = new StringBuilder();
36 |
37 | for (int y = 0; y < output.GetLength(1); y++)
38 | {
39 | char[] lineChars = new char[output.GetLength(0)];
40 | for (int i = 0; i < lineChars.Length; i++)
41 | {
42 | lineChars[i] = output[i, y];
43 | }
44 | buffer.AppendLine(new String(lineChars).TrimEnd());
45 | }
46 |
47 | return buffer.ToString().TrimEnd();
48 | }
49 |
50 | public static char[,] ReadConsoleRaw()
51 | {
52 | NativeInterface.CONSOLE_SCREEN_BUFFER_INFO screenBufferInfo = ConsoleInterface.getScreenBufferInfo();
53 |
54 | NativeInterface.SMALL_RECT readRegion = default(NativeInterface.SMALL_RECT);
55 | readRegion.Right = screenBufferInfo.dwSize.X;
56 | readRegion.Bottom = (short)(screenBufferInfo.dwCursorPosition.Y + 1); // read all lines from the top of the screen buffer to the current cursor position (inclusive)
57 |
58 | NativeInterface.CHAR_INFO[] charInfos = new NativeInterface.CHAR_INFO[screenBufferInfo.dwSize.X * screenBufferInfo.dwSize.Y];
59 | using (SafeHandle hConsoleOutput = ConsoleInterface.getConsoleOutputHandle())
60 | {
61 | ConsoleInterface.callWin32Func(() => NativeInterface.ReadConsoleOutput(hConsoleOutput, charInfos, screenBufferInfo.dwSize, default(NativeInterface.COORD), ref readRegion));
62 | }
63 |
64 | char[,] output = new char[screenBufferInfo.dwSize.X, readRegion.Bottom];
65 | for (int y = 0; y < readRegion.Bottom; y++)
66 | {
67 | for (int x = 0; x < screenBufferInfo.dwSize.X; x++)
68 | {
69 | output[x, y] = charInfos[y * screenBufferInfo.dwSize.X + x].UnicodeChar;
70 | }
71 | }
72 |
73 | return output;
74 | }
75 |
76 | public static void WriteConsole(string data)
77 | {
78 | using (SafeHandle hConsoleInput = ConsoleInterface.getConsoleInputHandle())
79 | {
80 | // we need 2x the number of characters because we need to simulate key press and key release events for each character
81 | NativeInterface.INPUT_RECORD[] inputRecords = new NativeInterface.INPUT_RECORD[data.Length * 2];
82 | for (int i = 0; i < data.Length; i++)
83 | {
84 | NativeInterface.KEY_EVENT_RECORD keyPressEvent = default(NativeInterface.KEY_EVENT_RECORD);
85 | keyPressEvent.bKeyDown = true;
86 | keyPressEvent.wRepeatCount = 1;
87 | keyPressEvent.UnicodeChar = data[i];
88 |
89 | NativeInterface.KEY_EVENT_RECORD keyReleaseEvent = keyPressEvent; // same values for all fields as the key press event, but with the bKeyDown field as "false"
90 | keyReleaseEvent.bKeyDown = false;
91 |
92 | inputRecords[i * 2].EventType = NativeInterface.KEY_EVENT;
93 | inputRecords[i * 2].Event = keyPressEvent;
94 | inputRecords[i * 2 + 1].EventType = NativeInterface.KEY_EVENT;
95 | inputRecords[i * 2 + 1].Event = keyReleaseEvent;
96 | }
97 |
98 | int recordsWritten;
99 | ConsoleInterface.callWin32Func(() => NativeInterface.WriteConsoleInput(hConsoleInput, inputRecords, inputRecords.Length, out recordsWritten));
100 | }
101 | }
102 |
103 | public static void AttachToConsole(int processId, TimeSpan timeout)
104 | {
105 | ConsoleInterface.callWin32Func(() => NativeInterface.FreeConsole());
106 | DateTime startTime = DateTime.Now;
107 | do
108 | {
109 | if (NativeInterface.AttachConsole(processId))
110 | {
111 | return;
112 | }
113 | } while (startTime + timeout > DateTime.Now);
114 | throw new TimeoutException();
115 | }
116 |
117 | public static void ClearConsole()
118 | {
119 | using (SafeHandle hConsoleOutput = ConsoleInterface.getConsoleOutputHandle())
120 | {
121 | // get dimensions of the screen buffer
122 | NativeInterface.CONSOLE_SCREEN_BUFFER_INFO screenBufferInfo = ConsoleInterface.getScreenBufferInfo();
123 |
124 | int charsToWrite = screenBufferInfo.dwSize.X * screenBufferInfo.dwSize.Y;
125 | int charsWritten;
126 | ConsoleInterface.callWin32Func(() => NativeInterface.FillConsoleOutputCharacter(hConsoleOutput, ConsoleInterface.SPACE_CHAR, charsToWrite, default(NativeInterface.COORD), out charsWritten));
127 | ConsoleInterface.callWin32Func(() => NativeInterface.SetConsoleCursorPosition(hConsoleOutput, default(NativeInterface.COORD)));
128 | }
129 | }
130 |
131 | #endregion Public Interface
132 |
133 | #region Helper Methods
134 |
135 | private static void callWin32Func(Func func)
136 | {
137 | if (!func.Invoke())
138 | {
139 | throw new Win32Exception();
140 | }
141 | }
142 |
143 | private static SafeHandle getConsoleHandle(int nStdHandle)
144 | {
145 | IntPtr hConsole = NativeInterface.GetStdHandle(nStdHandle);
146 | SafeHandle handle = new ConsoleHandle(hConsole, true);
147 | if (handle.IsInvalid)
148 | {
149 | throw new Win32Exception();
150 | }
151 |
152 | return handle;
153 | }
154 |
155 | private static SafeHandle getConsoleInputHandle()
156 | {
157 | return ConsoleInterface.getConsoleHandle(NativeInterface.STD_INPUT_HANDLE);
158 | }
159 |
160 | private static SafeHandle getConsoleOutputHandle()
161 | {
162 | return ConsoleInterface.getConsoleHandle(NativeInterface.STD_OUTPUT_HANDLE);
163 | }
164 |
165 | private static NativeInterface.CONSOLE_SCREEN_BUFFER_INFO getScreenBufferInfo()
166 | {
167 | using (SafeHandle hConsoleOutput = ConsoleInterface.getConsoleOutputHandle())
168 | {
169 | NativeInterface.CONSOLE_SCREEN_BUFFER_INFO screenBufferInfo = default(NativeInterface.CONSOLE_SCREEN_BUFFER_INFO);
170 | ConsoleInterface.callWin32Func(() => NativeInterface.GetConsoleScreenBufferInfo(hConsoleOutput, out screenBufferInfo));
171 | return screenBufferInfo;
172 | }
173 | }
174 |
175 | #endregion Helper Methods
176 |
177 | private class ConsoleHandle : SafeHandleMinusOneIsInvalid
178 | {
179 | public ConsoleHandle(IntPtr hConsole, bool ownsHandle)
180 | : base(ownsHandle)
181 | {
182 | this.SetHandle(hConsole);
183 | }
184 |
185 | protected override bool ReleaseHandle()
186 | {
187 | return true; // don't close console handles as that will be done for us at process termination time
188 | }
189 | }
190 |
191 | private static class NativeInterface
192 | {
193 | [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
194 | public struct CHAR_INFO
195 | {
196 | public char UnicodeChar;
197 | public short Attributes;
198 | }
199 |
200 | [StructLayout(LayoutKind.Sequential)]
201 | public struct SMALL_RECT
202 | {
203 | public short Left;
204 | public short Top;
205 | public short Right;
206 | public short Bottom;
207 | }
208 |
209 | [StructLayout(LayoutKind.Sequential)]
210 | public struct COORD
211 | {
212 | public short X;
213 | public short Y;
214 | }
215 |
216 | [StructLayout(LayoutKind.Sequential)]
217 | public struct INPUT_RECORD
218 | {
219 | public short EventType;
220 | public KEY_EVENT_RECORD Event;
221 | }
222 |
223 | [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
224 | public struct KEY_EVENT_RECORD
225 | {
226 | public bool bKeyDown;
227 | public short wRepeatCount;
228 | public short wVirtualKeyCode;
229 | public short wVirtualScanCode;
230 | public char UnicodeChar;
231 | public int dwControlKeyState;
232 | }
233 |
234 | [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
235 | public struct CONSOLE_SCREEN_BUFFER_INFO
236 | {
237 | public NativeInterface.COORD dwSize;
238 | public NativeInterface.COORD dwCursorPosition;
239 | public short wAttributes;
240 | public NativeInterface.SMALL_RECT srWindow;
241 | public NativeInterface.COORD dwMaximumWindowSize;
242 | }
243 |
244 | [DllImport("kernel32", SetLastError = true)]
245 | public static extern bool SetConsoleMode(SafeHandle hConsoleHandle, int dwMode);
246 |
247 | [DllImport("kernel32", SetLastError = true)]
248 | public static extern bool GetConsoleMode(SafeHandle hConsoleHandle, out int dwMode);
249 |
250 | [DllImport("kernel32", SetLastError = true)]
251 | public static extern bool ReadConsoleOutput(SafeHandle hConsoleHandle, [Out] CHAR_INFO[] lpBuffer, COORD dwBufferSize, COORD dwBufferCoord, ref SMALL_RECT lpReadRegion);
252 |
253 | [DllImport("kernel32", SetLastError = true)]
254 | public static extern IntPtr GetStdHandle(int nStdHandle);
255 |
256 | [DllImport("kernel32", SetLastError = true)]
257 | public static extern bool FreeConsole();
258 |
259 | [DllImport("kernel32", SetLastError = true)]
260 | public static extern bool AttachConsole(int dwProcessId);
261 |
262 | [DllImport("kernel32", SetLastError = true)]
263 | public static extern int GetConsoleProcessList([Out] int[] lpdwProcessList, int dwProcessCount);
264 |
265 | [DllImport("kernel32", SetLastError = true)]
266 | public static extern bool WriteConsoleInput(SafeHandle hConsoleInput, [In] INPUT_RECORD[] lpBuffer, int nLength, out int lpNumberOfEventsWritten);
267 |
268 | [DllImport("kernel32", CharSet = CharSet.Unicode, SetLastError = true)]
269 | public static extern bool FillConsoleOutputCharacter(SafeHandle hConsoleOutput, char cCharacter, int nLength, NativeInterface.COORD dwWriteCoord, out int lpNumberOfCharsWritten);
270 |
271 | [DllImport("kernel32", SetLastError = true)]
272 | public static extern bool GetConsoleScreenBufferInfo(SafeHandle hConsoleOutput, out NativeInterface.CONSOLE_SCREEN_BUFFER_INFO result);
273 |
274 | [DllImport("kernel32", SetLastError = true)]
275 | public static extern bool SetConsoleCursorPosition(SafeHandle hConsoleOutput, NativeInterface.COORD dwCursorPosition);
276 |
277 | public const int ENABLE_ECHO_INPUT = 0x4;
278 |
279 | public const int STD_INPUT_HANDLE = -10;
280 | public const int STD_OUTPUT_HANDLE = -11;
281 |
282 | public const int KEY_EVENT = 0x1;
283 |
284 | }
285 | }
286 | }
287 |
--------------------------------------------------------------------------------
/Cbonnell.DotNetExpect/OperationFailedException.cs:
--------------------------------------------------------------------------------
1 | /*
2 | DotNetExpect
3 | Copyright (c) Corey Bonnell, All rights reserved.
4 |
5 | This library is free software; you can redistribute it and/or
6 | modify it under the terms of the GNU Lesser General Public
7 | License as published by the Free Software Foundation; either
8 | version 3.0 of the License, or (at your option) any later version.
9 |
10 | This library is distributed in the hope that it will be useful,
11 | but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 | Lesser General Public License for more details.
14 |
15 | You should have received a copy of the GNU Lesser General Public
16 | License along with this library. */
17 |
18 | using System;
19 |
20 | namespace Cbonnell.DotNetExpect
21 | {
22 | ///
23 | /// Represents an error that occurred when accessing a child process's console input or output.
24 | ///
25 | public class OperationFailedException : Exception
26 | {
27 | private const string MESSAGE_FMT = "An operation failed with the following error: {0}";
28 |
29 | internal OperationFailedException(CommandResult reason)
30 | : base(String.Format(OperationFailedException.MESSAGE_FMT, reason))
31 | {
32 | this.Reason = reason;
33 | }
34 |
35 | ///
36 | /// Retrieves the reason for the error.
37 | ///
38 | public CommandResult Reason
39 | {
40 | get;
41 | private set;
42 | }
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/Cbonnell.DotNetExpect/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | /*
2 | DotNetExpect
3 | Copyright (c) Corey Bonnell, All rights reserved.
4 |
5 | This library is free software; you can redistribute it and/or
6 | modify it under the terms of the GNU Lesser General Public
7 | License as published by the Free Software Foundation; either
8 | version 3.0 of the License, or (at your option) any later version.
9 |
10 | This library is distributed in the hope that it will be useful,
11 | but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 | Lesser General Public License for more details.
14 |
15 | You should have received a copy of the GNU Lesser General Public
16 | License along with this library. */
17 |
18 | using System.Reflection;
19 | using System.Runtime.InteropServices;
20 |
21 | // General Information about an assembly is controlled through the following
22 | // set of attributes. Change these attribute values to modify the information
23 | // associated with an assembly.
24 | [assembly: AssemblyTitle("Cbonnell.DotNetExpect")]
25 | [assembly: AssemblyDescription("An Expect-inspired .NET library for accessing console I/O of processes.")]
26 | [assembly: AssemblyConfiguration("")]
27 | [assembly: AssemblyCompany("Cbonnell")]
28 | [assembly: AssemblyProduct("Cbonnell.DotNetExpect")]
29 | [assembly: AssemblyCopyright("Copyright © Corey Bonnell 2015")]
30 | [assembly: AssemblyTrademark("")]
31 | [assembly: AssemblyCulture("")]
32 |
33 | // Setting ComVisible to false makes the types in this assembly not visible
34 | // to COM components. If you need to access a type in this assembly from
35 | // COM, set the ComVisible attribute to true on that type.
36 | [assembly: ComVisible(false)]
37 |
38 | // The following GUID is for the ID of the typelib if this project is exposed to COM
39 | [assembly: Guid("f71b6a09-e6ea-4d51-90df-25c9709d2cdd")]
40 |
41 | // Version information for an assembly consists of the following four values:
42 | //
43 | // Major Version
44 | // Minor Version
45 | // Build Number
46 | // Revision
47 | //
48 | // You can specify all the values or you can default the Build and Revision Numbers
49 | // by using the '*' as shown below:
50 | // [assembly: AssemblyVersion("1.0.*")]
51 | [assembly: AssemblyVersion("1.0.0.0")]
52 | [assembly: AssemblyFileVersion("1.0.0.0")]
53 |
--------------------------------------------------------------------------------
/Cbonnell.DotNetExpect/ProxyProcess.cs:
--------------------------------------------------------------------------------
1 | /*
2 | DotNetExpect
3 | Copyright (c) Corey Bonnell, All rights reserved.
4 |
5 | This library is free software; you can redistribute it and/or
6 | modify it under the terms of the GNU Lesser General Public
7 | License as published by the Free Software Foundation; either
8 | version 3.0 of the License, or (at your option) any later version.
9 |
10 | This library is distributed in the hope that it will be useful,
11 | but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 | Lesser General Public License for more details.
14 |
15 | You should have received a copy of the GNU Lesser General Public
16 | License along with this library. */
17 |
18 | using System;
19 | using System.Diagnostics;
20 | using System.IO;
21 | using System.IO.Pipes;
22 |
23 | namespace Cbonnell.DotNetExpect
24 | {
25 | internal enum ProxyCommand
26 | {
27 | StartProcess = 0,
28 | SetPid,
29 | KillProcess,
30 | ClearConsole,
31 | ReadConsole,
32 | WriteConsole,
33 | AttachConsole,
34 | GetPid,
35 | GetProcessExitCode,
36 | GetHasProcessExited,
37 | }
38 |
39 | internal class ProxyProcess
40 | {
41 | private readonly NamedPipeClientStream commandPipe;
42 | private readonly BinaryReader commandReader;
43 | private readonly BinaryWriter commandWriter;
44 | private Process process = null;
45 | private bool spawnedProcess = false;
46 |
47 | public ProxyProcess(string commandPipeName)
48 | {
49 | this.commandPipe = new NamedPipeClientStream(commandPipeName);
50 | this.commandReader = new BinaryReader(this.commandPipe);
51 | this.commandWriter = new BinaryWriter(this.commandPipe);
52 | }
53 |
54 | public void Run()
55 | {
56 | this.commandPipe.Connect();
57 |
58 | bool shouldContinue = true;
59 | try
60 | {
61 | while (shouldContinue)
62 | {
63 | int opcode = this.commandReader.ReadByte();
64 | CommandResult result = CommandResult.GeneralFailure;
65 | bool shouldWriteResult = true; // individual opcodes can set this to false to return specialized data
66 |
67 | switch ((ProxyCommand)opcode)
68 | {
69 | case ProxyCommand.StartProcess:
70 | if (this.process == null)
71 | {
72 | this.process = new Process();
73 | this.process.StartInfo.FileName = this.commandReader.ReadString();
74 | this.process.StartInfo.Arguments = this.commandReader.ReadString();
75 | this.process.StartInfo.WorkingDirectory = this.commandReader.ReadString();
76 | this.process.StartInfo.UseShellExecute = false;
77 | this.process.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
78 | try
79 | {
80 | this.process.Start();
81 | this.spawnedProcess = true;
82 | result = CommandResult.Success;
83 | }
84 | catch (Exception)
85 | {
86 | result = CommandResult.CouldNotSpawnChild;
87 | this.process.Dispose();
88 | this.process = null;
89 | }
90 | }
91 | else
92 | {
93 | result = CommandResult.ChildAlreadySpawned;
94 | }
95 | break;
96 | case ProxyCommand.SetPid:
97 | if (this.process == null)
98 | {
99 | try
100 | {
101 | int pid = this.commandReader.ReadInt32();
102 | this.process = Process.GetProcessById(pid);
103 | result = CommandResult.Success;
104 | }
105 | catch (Exception)
106 | {
107 | result = CommandResult.ProcessDoesNotExist;
108 | }
109 | }
110 | break;
111 | case ProxyCommand.KillProcess:
112 | if (this.process != null)
113 | {
114 | this.process.Kill();
115 | this.process.WaitForExit();
116 |
117 | this.commandWriter.Write((byte)CommandResult.Success);
118 | this.commandWriter.Write(this.process.ExitCode);
119 |
120 | this.process.Dispose();
121 | this.process = null;
122 |
123 | shouldWriteResult = false;
124 | shouldContinue = false;
125 | }
126 | else
127 | {
128 | result = CommandResult.ChildNotSpawned;
129 | }
130 | break;
131 | case ProxyCommand.AttachConsole:
132 | int timeoutMs = this.commandReader.ReadInt32();
133 |
134 | if (this.process != null)
135 | {
136 | ConsoleInterface.AttachToConsole(this.process.Id, TimeSpan.FromMilliseconds(timeoutMs));
137 | result = CommandResult.Success;
138 | }
139 | else
140 | {
141 | result = CommandResult.ChildNotSpawned;
142 | }
143 | break;
144 | case ProxyCommand.ReadConsole:
145 | string outputData = ConsoleInterface.ReadConsole();
146 | shouldWriteResult = false;
147 | this.commandWriter.Write((byte)CommandResult.Success);
148 | this.commandWriter.Write(outputData);
149 | break;
150 | case ProxyCommand.WriteConsole:
151 | string inputData = this.commandReader.ReadString();
152 | ConsoleInterface.WriteConsole(inputData);
153 | result = CommandResult.Success;
154 | break;
155 | case ProxyCommand.ClearConsole:
156 | ConsoleInterface.ClearConsole();
157 | result = CommandResult.Success;
158 | break;
159 | case ProxyCommand.GetHasProcessExited:
160 | if (this.process != null)
161 | {
162 | this.commandWriter.Write((byte)CommandResult.Success);
163 | this.commandWriter.Write(this.process.HasExited);
164 | shouldWriteResult = false;
165 | }
166 | else
167 | {
168 | result = CommandResult.ChildNotSpawned;
169 | }
170 | break;
171 | case ProxyCommand.GetPid:
172 | if (this.process != null)
173 | {
174 | this.commandWriter.Write((byte)CommandResult.Success);
175 | this.commandWriter.Write(this.process.Id);
176 | shouldWriteResult = false;
177 | }
178 | else
179 | {
180 | result = CommandResult.ChildNotSpawned;
181 | }
182 | break;
183 | case ProxyCommand.GetProcessExitCode:
184 | if (this.process != null)
185 | {
186 | if (this.process.HasExited)
187 | {
188 | this.commandWriter.Write((byte)CommandResult.Success);
189 | this.commandWriter.Write(this.process.ExitCode);
190 | shouldWriteResult = false;
191 | }
192 | else
193 | {
194 | result = CommandResult.ChildHasNotExited;
195 | }
196 | }
197 | else
198 | {
199 | result = CommandResult.ChildNotSpawned;
200 | }
201 | break;
202 | default:
203 | result = CommandResult.GeneralFailure;
204 | break;
205 | }
206 |
207 | if (shouldWriteResult)
208 | {
209 | this.commandWriter.Write((byte)result);
210 | }
211 | this.commandWriter.Flush();
212 | }
213 | }
214 | finally // ensure that the child process is killed if we exit, but only if we spawned the process
215 | {
216 | if (this.process != null && this.spawnedProcess)
217 | {
218 | this.process.Kill();
219 | }
220 | }
221 | }
222 | }
223 | }
224 |
--------------------------------------------------------------------------------
/Cbonnell.DotNetExpect/ProxyProcessManager.cs:
--------------------------------------------------------------------------------
1 | /*
2 | DotNetExpect
3 | Copyright (c) Corey Bonnell, All rights reserved.
4 |
5 | This library is free software; you can redistribute it and/or
6 | modify it under the terms of the GNU Lesser General Public
7 | License as published by the Free Software Foundation; either
8 | version 3.0 of the License, or (at your option) any later version.
9 |
10 | This library is distributed in the hope that it will be useful,
11 | but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 | Lesser General Public License for more details.
14 |
15 | You should have received a copy of the GNU Lesser General Public
16 | License along with this library. */
17 |
18 | using System;
19 | using System.ComponentModel;
20 | using System.Diagnostics;
21 | using System.IO;
22 | using System.IO.Pipes;
23 | using System.Reflection;
24 | using System.Runtime.InteropServices;
25 |
26 | namespace Cbonnell.DotNetExpect
27 | {
28 | internal class ProxyProcessManager : IDisposable
29 | {
30 | private const string PIPE_NAME_FMT = "Cbonnell.DotNetExpect.{0}";
31 | private const string POWERSHELL_COMMAND_LINE = "powershell.exe -Command \"$asm = [System.Reflection.Assembly]::LoadFile('{0}'); $proxyType = $asm.GetType('Cbonnell.DotNetExpect.ProxyProcess'); $proxy = [System.Activator]::CreateInstance($proxyType, @('{1}')); $proxy.Run() | Out-Null;\"";
32 |
33 | private readonly NamedPipeServerStream commandPipe;
34 | private readonly BinaryReader commandPipeReader;
35 | private readonly BinaryWriter commandPipeWriter;
36 | private readonly string commandPipeName;
37 |
38 | private Process proxyProcess = null;
39 |
40 | public ProxyProcessManager()
41 | {
42 | this.commandPipeName = String.Format(ProxyProcessManager.PIPE_NAME_FMT, Guid.NewGuid());
43 | this.commandPipe = new NamedPipeServerStream(this.commandPipeName);
44 | this.commandPipeReader = new BinaryReader(this.commandPipe);
45 | this.commandPipeWriter = new BinaryWriter(this.commandPipe);
46 | }
47 |
48 | public BinaryReader CommandPipeReader
49 | {
50 | get
51 | {
52 | return this.commandPipeReader;
53 | }
54 | }
55 |
56 | public BinaryWriter CommandPipeWriter
57 | {
58 | get
59 | {
60 | return this.commandPipeWriter;
61 | }
62 | }
63 |
64 | public void Start()
65 | {
66 | NativeMethods.STARTUPINFO startInfo = default(NativeMethods.STARTUPINFO);
67 | startInfo.cb = Marshal.SizeOf(startInfo);
68 | startInfo.dwFlags = NativeMethods.STARTF_USE_SHOWWINDOW;
69 | startInfo.wShowWindow = NativeMethods.SW_HIDE;
70 |
71 | string commandLine = String.Format(ProxyProcessManager.POWERSHELL_COMMAND_LINE, Assembly.GetExecutingAssembly().Location, this.commandPipeName);
72 |
73 | NativeMethods.PROCESS_INFORMATION procInfo;
74 | if (!NativeMethods.CreateProcess(null, commandLine, IntPtr.Zero, IntPtr.Zero, false, NativeMethods.CREATE_NEW_CONSOLE, IntPtr.Zero, null, ref startInfo, out procInfo))
75 | {
76 | throw new Win32Exception();
77 | }
78 |
79 | NativeMethods.CloseHandle(procInfo.hProcess);
80 | NativeMethods.CloseHandle(procInfo.hThread);
81 | this.proxyProcess = Process.GetProcessById(procInfo.dwProcessId);
82 | this.commandPipe.WaitForConnection();
83 | }
84 |
85 | public void Dispose()
86 | {
87 | this.commandPipe.Dispose();
88 | if (this.proxyProcess != null)
89 | {
90 | this.proxyProcess.Dispose();
91 | }
92 | }
93 |
94 | private static class NativeMethods
95 | {
96 | [StructLayout(LayoutKind.Sequential)]
97 | public struct PROCESS_INFORMATION
98 | {
99 | public IntPtr hProcess;
100 | public IntPtr hThread;
101 | public int dwProcessId;
102 | public int dwThreadId;
103 | }
104 |
105 | [StructLayout(LayoutKind.Sequential)]
106 | public struct STARTUPINFO
107 | {
108 | public int cb;
109 | public string lpReserved;
110 | public string lpDesktop;
111 | public string lpTitle;
112 | public int dwX;
113 | public int dwY;
114 | public int dwXSize;
115 | public int dwYSize;
116 | public int dwXCountChars;
117 | public int dwYCountChars;
118 | public int dwFillAttribute;
119 | public int dwFlags;
120 | public short wShowWindow;
121 | public short cbReserved2;
122 | public IntPtr lpReserved2;
123 | public IntPtr hStdInput;
124 | public IntPtr hStdOutput;
125 | public IntPtr hStdError;
126 | }
127 |
128 | [DllImport("kernel32", SetLastError = true)]
129 | public static extern bool CreateProcess(string lpApplicationName, string lpCommandLine, IntPtr lpProcessAttributes, IntPtr lpThreadAttributes, bool bInheritHandles, uint dwCreationFlags, IntPtr lpEnvironment, string lpCurrentDirectory, ref STARTUPINFO lpStartupInfo, out PROCESS_INFORMATION lpProcessInformation);
130 |
131 | [DllImport("kernel32", SetLastError = true)]
132 | public static extern bool CloseHandle(IntPtr hObject);
133 |
134 | public const int STARTF_USE_SHOWWINDOW = 0x1;
135 | public const int SW_HIDE = 0;
136 | public const int CREATE_NEW_CONSOLE = 0x10;
137 | }
138 | }
139 | }
140 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | GNU LESSER GENERAL PUBLIC LICENSE
2 | Version 3, 29 June 2007
3 |
4 | Copyright (C) 2007 Free Software Foundation, Inc.
5 | Everyone is permitted to copy and distribute verbatim copies
6 | of this license document, but changing it is not allowed.
7 |
8 |
9 | This version of the GNU Lesser General Public License incorporates
10 | the terms and conditions of version 3 of the GNU General Public
11 | License, supplemented by the additional permissions listed below.
12 |
13 | 0. Additional Definitions.
14 |
15 | As used herein, "this License" refers to version 3 of the GNU Lesser
16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU
17 | General Public License.
18 |
19 | "The Library" refers to a covered work governed by this License,
20 | other than an Application or a Combined Work as defined below.
21 |
22 | An "Application" is any work that makes use of an interface provided
23 | by the Library, but which is not otherwise based on the Library.
24 | Defining a subclass of a class defined by the Library is deemed a mode
25 | of using an interface provided by the Library.
26 |
27 | A "Combined Work" is a work produced by combining or linking an
28 | Application with the Library. The particular version of the Library
29 | with which the Combined Work was made is also called the "Linked
30 | Version".
31 |
32 | The "Minimal Corresponding Source" for a Combined Work means the
33 | Corresponding Source for the Combined Work, excluding any source code
34 | for portions of the Combined Work that, considered in isolation, are
35 | based on the Application, and not on the Linked Version.
36 |
37 | The "Corresponding Application Code" for a Combined Work means the
38 | object code and/or source code for the Application, including any data
39 | and utility programs needed for reproducing the Combined Work from the
40 | Application, but excluding the System Libraries of the Combined Work.
41 |
42 | 1. Exception to Section 3 of the GNU GPL.
43 |
44 | You may convey a covered work under sections 3 and 4 of this License
45 | without being bound by section 3 of the GNU GPL.
46 |
47 | 2. Conveying Modified Versions.
48 |
49 | If you modify a copy of the Library, and, in your modifications, a
50 | facility refers to a function or data to be supplied by an Application
51 | that uses the facility (other than as an argument passed when the
52 | facility is invoked), then you may convey a copy of the modified
53 | version:
54 |
55 | a) under this License, provided that you make a good faith effort to
56 | ensure that, in the event an Application does not supply the
57 | function or data, the facility still operates, and performs
58 | whatever part of its purpose remains meaningful, or
59 |
60 | b) under the GNU GPL, with none of the additional permissions of
61 | this License applicable to that copy.
62 |
63 | 3. Object Code Incorporating Material from Library Header Files.
64 |
65 | The object code form of an Application may incorporate material from
66 | a header file that is part of the Library. You may convey such object
67 | code under terms of your choice, provided that, if the incorporated
68 | material is not limited to numerical parameters, data structure
69 | layouts and accessors, or small macros, inline functions and templates
70 | (ten or fewer lines in length), you do both of the following:
71 |
72 | a) Give prominent notice with each copy of the object code that the
73 | Library is used in it and that the Library and its use are
74 | covered by this License.
75 |
76 | b) Accompany the object code with a copy of the GNU GPL and this license
77 | document.
78 |
79 | 4. Combined Works.
80 |
81 | You may convey a Combined Work under terms of your choice that,
82 | taken together, effectively do not restrict modification of the
83 | portions of the Library contained in the Combined Work and reverse
84 | engineering for debugging such modifications, if you also do each of
85 | the following:
86 |
87 | a) Give prominent notice with each copy of the Combined Work that
88 | the Library is used in it and that the Library and its use are
89 | covered by this License.
90 |
91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license
92 | document.
93 |
94 | c) For a Combined Work that displays copyright notices during
95 | execution, include the copyright notice for the Library among
96 | these notices, as well as a reference directing the user to the
97 | copies of the GNU GPL and this license document.
98 |
99 | d) Do one of the following:
100 |
101 | 0) Convey the Minimal Corresponding Source under the terms of this
102 | License, and the Corresponding Application Code in a form
103 | suitable for, and under terms that permit, the user to
104 | recombine or relink the Application with a modified version of
105 | the Linked Version to produce a modified Combined Work, in the
106 | manner specified by section 6 of the GNU GPL for conveying
107 | Corresponding Source.
108 |
109 | 1) Use a suitable shared library mechanism for linking with the
110 | Library. A suitable mechanism is one that (a) uses at run time
111 | a copy of the Library already present on the user's computer
112 | system, and (b) will operate properly with a modified version
113 | of the Library that is interface-compatible with the Linked
114 | Version.
115 |
116 | e) Provide Installation Information, but only if you would otherwise
117 | be required to provide such information under section 6 of the
118 | GNU GPL, and only to the extent that such information is
119 | necessary to install and execute a modified version of the
120 | Combined Work produced by recombining or relinking the
121 | Application with a modified version of the Linked Version. (If
122 | you use option 4d0, the Installation Information must accompany
123 | the Minimal Corresponding Source and Corresponding Application
124 | Code. If you use option 4d1, you must provide the Installation
125 | Information in the manner specified by section 6 of the GNU GPL
126 | for conveying Corresponding Source.)
127 |
128 | 5. Combined Libraries.
129 |
130 | You may place library facilities that are a work based on the
131 | Library side by side in a single library together with other library
132 | facilities that are not Applications and are not covered by this
133 | License, and convey such a combined library under terms of your
134 | choice, if you do both of the following:
135 |
136 | a) Accompany the combined library with a copy of the same work based
137 | on the Library, uncombined with any other library facilities,
138 | conveyed under the terms of this License.
139 |
140 | b) Give prominent notice with the combined library that part of it
141 | is a work based on the Library, and explaining where to find the
142 | accompanying uncombined form of the same work.
143 |
144 | 6. Revised Versions of the GNU Lesser General Public License.
145 |
146 | The Free Software Foundation may publish revised and/or new versions
147 | of the GNU Lesser General Public License from time to time. Such new
148 | versions will be similar in spirit to the present version, but may
149 | differ in detail to address new problems or concerns.
150 |
151 | Each version is given a distinguishing version number. If the
152 | Library as you received it specifies that a certain numbered version
153 | of the GNU Lesser General Public License "or any later version"
154 | applies to it, you have the option of following the terms and
155 | conditions either of that published version or of any later version
156 | published by the Free Software Foundation. If the Library as you
157 | received it does not specify a version number of the GNU Lesser
158 | General Public License, you may choose any version of the GNU Lesser
159 | General Public License ever published by the Free Software Foundation.
160 |
161 | If the Library as you received it specifies that a proxy can decide
162 | whether future versions of the GNU Lesser General Public License shall
163 | apply, that proxy's public statement of acceptance of any version is
164 | permanent authorization for you to choose that version for the
165 | Library.
166 |
167 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # DotNetExpect
2 |
3 |
4 | ### What is DotNetExpect?
5 | Inspired by the design of the [Expect library](http://en.wikipedia.org/wiki/Expect), DotNetExpect is a .NET library that provides console input/control of console-based Windows applications. Unlike other solutions that can only redirect the standard streams of child processes, DotNetExpect directly accesses the console screen buffer of applications so that regardless of the mechanism the application uses to write to the console (either by writing to the standard streams or by invoking low-level console output functions), DotNetExpect is able to work with a much wider range of console applications.
6 |
7 | DotNetExpect is designed with the intention of exposing a simple, easy to use API so that automation of console applications becomes a trivial task.
8 |
9 | ### How do I use DotNetExpect?
10 | The DotNetExpect library exposes the primary class of the library interface, which is named `ChildProcess`. All operations regarding the creation/termination of console applications as well as reading and writing to an application's console is performed using this class.
11 |
12 | Here is a short example showing how the Windows Telnet client application can be automated to log into a Linux machine and retrieve a directory listing:
13 |
14 | ```csharp
15 | // Create a new instance of ChildProcess. This will spawn telnet.exe and connect its console to the library.
16 | // ChildProcess implements IDisposable, so generally you will want to instantiate this class in a using statement.
17 | using (ChildProcess childProc = new ChildProcess("telnet.exe", "192.168.1.1"))
18 | {
19 | // Wait for the login prompt to appear
20 | childProc.Read("login:");
21 |
22 | // Write the user name with which to login to the console input
23 | childProc.WriteLine("root");
24 |
25 | // Wait for the password prompt to appear
26 | childProc.Read("Password:");
27 |
28 | // Write the password
29 | childProc.WriteLine("MySecretPassword");
30 |
31 | // Wait for the root shell prompt to appear
32 | childProc.Read("#");
33 |
34 | // Issue the "ls" command to output the directory contents
35 | childProc.WriteLine("ls");
36 |
37 | // Wait until the root shell prompt appears and return the directory contents
38 | string dirContents = childProc.Read("#");
39 |
40 | // Display the directory contents to our console
41 | Console.WriteLine(dirContents);
42 | }
43 | ```
44 |
45 | Many other properties and methods are exposed on the `ChildProcess` class, including regular expression output matching and process management (such as terminating a child process and retrieving its exit code, etc.). Documentation is provided for each public member of the library and the library was designed to leverage Visual Studio's IntelliSense functionality for discoverability and ease of use.
46 |
47 | ### How do I obtain DotNetExpect?
48 | DotNetExpect is available as a [NuGet package](https://www.nuget.org/packages/Cbonnell.DotNetExpect/). Alternatively, the library can be built from source.
49 |
50 | ### How do I build DotNetExpect?
51 | DotNetExpect is Visual Studio 2013 project, so it is possible to build the project by merely opening up the solution (.sln) file for the library and building it or invoking MSBuild from the command line.
52 |
53 | ### What are the system requirements for using DotNetExpect?
54 | DotNetExpect is written against .NET framework 3.5 (client profile), so that version of the framework or higher must be installed. DotNetExpect uses PowerShell as a proxy process (see the section "How does DotNetExpect work?" below for details), so PowerShell must be installed and on the system PATH. Any version of PowerShell should work.
55 |
56 | ### What is the license for DotNetExpect?
57 | DotNetExpect is licensed under the LGPL version 3.0.
58 |
59 | ### Are there tests available?
60 | A small suite of unit and integration tests is included in the Visual Studio solution. These tests are written against NUnit 2.6.4, so you will need to have NUnit installed to build and run these tests.
61 |
62 | ### Are there any limitations of DotNetExpect?
63 | The chief limitation of DotNetExpect is that the output of console applications may be lost if huge amounts of data are being written to the console. This is due to how DotNetExpect interacts with console applications to capture thier output. The contents of the console application's screen buffer are constantly being captured by the library, but there may be instances where some output is lost due to the output "scrolling out" of the buffer before it can be captured.
64 |
65 | A second limitation is that "console-like" applications, such as PuTTY, cannot be manipulated using this library. This is due to PuTTY (and some other applications) actually not being a console application, but rather a Windows graphical application that happens to look like a console application. Therefore, the Console API functions that DotNetExpect uses have no effect on these types of applications.
66 |
67 | ### How do I report bugs or request enhancements?
68 | Please [create an issue](https://github.com/CBonnell/dotnetexpect/issues).
69 |
70 | ### How does DotNetExpect work?
71 | When an instance of `ChildProcess` is created, a randomly-named named pipe is created and a "proxy process" is launched. The proxy process that is created is PowerShell, which then loads the DotNetExpect assembly and invokes the code in the DotNetExpect library that runs the proxy process server. The proxy process server connects to the named pipe and services commands sent from the library. Whenever a method or property is called on `ChildProcess`, that request is sent via the named pipe to the proxy process, which then services the request.
72 |
73 | The rationale for using a proxy process is so that graphical applications (which do not have a console) or console applications that still want to have console input/output of their own will function normally, as the proxy process is created with its own console entirely separate from the parent (calling) process. It is the proxy process that actually spawns the child process and shares its console with the child process. PowerShell was chosen due to its ability to dynamically load and call into .NET assemblies, obviating the need to author a separate executable for the proxy process. In this regard, PowerShell acts as a host process for running .NET code.
74 |
75 |
76 |
77 |
--------------------------------------------------------------------------------