Skip to content

Commit 0ea22fb

Browse files
committed
Added GatherInputArguments
1 parent f4adcd9 commit 0ea22fb

File tree

8 files changed

+60
-37
lines changed

8 files changed

+60
-37
lines changed

README.md

Lines changed: 29 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -8,25 +8,25 @@ PSWebApi is a simple library for building ASP.NET Web APIs (RESTful Services) by
88
Similar to the sister repository [DbWebApi](https://github.com/DataBooster/DbWebApi), any managed http client can invoke PowerShell scripts, batch files and executables through the PS-WebApi as the following,
99

1010
**PowerShell**:
11-
- `http://`localhost:1608/**ps.json**/*ps-scripts/test-args.ps1?p1=1&=arg...*
11+
- `http://base-uri`/**ps.json**/*ps-scripts/test-args.ps1?p1=1&=arg...*
1212
*(returns the result object as JSON)*
13-
- `http://`localhost:1608/**ps.xml**/*ps-scripts/test-args.ps1?p1=1&=arg...*
13+
- `http://base-uri`/**ps.xml**/*ps-scripts/test-args.ps1?p1=1&=arg...*
1414
*(returns the result object as XML)*
15-
- `http://`localhost:1608/**ps.csv**/*ps-scripts/test-args.ps1?p1=1&=arg...*
15+
- `http://base-uri`/**ps.csv**/*ps-scripts/test-args.ps1?p1=1&=arg...*
1616
*(returns the result object as CSV)*
17-
- `http://`localhost:1608/**ps.html**/*ps-scripts/test-args.ps1?p1=1&=arg...*
17+
- `http://base-uri`/**ps.html**/*ps-scripts/test-args.ps1?p1=1&=arg...*
1818
*(returns the result object as HTML)*
19-
- `http://`localhost:1608/**ps.string**/*ps-scripts/test-args.ps1?p1=1&=arg...*
19+
- `http://base-uri`/**ps.string**/*ps-scripts/test-args.ps1?p1=1&=arg...*
2020
*(returns the result object as plain text)*
21-
- `http://`localhost:1608/**ps.null**/*ps-scripts/test-args.ps1?p1=1&=arg...*
21+
- `http://base-uri`/**ps.null**/*ps-scripts/test-args.ps1?p1=1&=arg...*
2222
*(discards the result and returns empty)*
2323

2424
**Batch**:
25-
- `http://`localhost:1608/**cmd**/*bat-scripts/test-args.bat?a1=1&=arg...*
25+
- `http://base-uri`/**cmd**/*bat-scripts/test-args.bat?a1=1&=arg...*
2626
*(captures the StdOut and returns it as plain text)*
2727

2828
**Executable**:
29-
- `http://`localhost:1608/**cmd**/*exe-programs/test-args.exe?a1=1&=arg...*
29+
- `http://base-uri`/**cmd**/*exe-programs/test-args.exe?a1=1&=arg...*
3030
*(captures the StdOut and returns it as plain text)*
3131

3232
Often times, in some intranet applications, some functional requirements can be quickly implemented by succinct PowerShell scripts or command line scripts. However, for some integratability reason, this practice was out of sight from most existing projects.
@@ -66,8 +66,8 @@ The only one configuration item that must be customized is the **ScriptRoot** in
6666
## HTTP Client
6767
#### Request-URI
6868

69-
If there is a PowerShell script "D:\scripts-root\Dept1\ps\test\demo1.ps1", the HTTP client should call it by URL like `http://localhost:1608/ps.json`/__Dept1/ps/test/demo1.ps1__?p1=1&=arg....
70-
If there is a batch file "D:\scripts-root\Dept1\bat\test\demo2.bat", the HTTP client should call it by URL like `http://localhost:1608/cmd`/__Dept1/bat/test/demo2.bat__?=1&=arg....
69+
If there is a PowerShell script "D:\scripts-root\Dept1\ps\test\demo1.ps1", the HTTP client should call it by URL like `http://base-uri/ps.json`/__Dept1/ps/test/demo1.ps1__?p1=1&=arg....
70+
If there is a batch file "D:\scripts-root\Dept1\bat\test\demo2.bat", the HTTP client should call it by URL like `http://base-uri/cmd`/__Dept1/bat/test/demo2.bat__?=1&=arg....
7171
Calling executable file follows the same pattern as batch file.
7272

7373
#### Response MediaType
@@ -226,21 +226,32 @@ When there is only one item (single value) in the array, the single JSON value c
226226
test-args.bat "single value argument"
227227
```
228228
229-
- ***Escaping***
230-
Due to the particularity of Windows command line argument parsing, PSWebApi uses the following rules by default to determine whether or not a string value must be surrounded by extra double quotation marks:
231-
- A string will remain unchanged if the command-line parser (CMD.EXE) can interpret it as a single argument;
229+
- ***Escaping and Quoting***
230+
Since there is no unified standard to escape and quote a Windows command line argument, it needs to be fully controlled by your own customized PSWebApi service *(please see a reference implementation [CmdArgumentResolver.cs](https://github.com/DataBooster/PS-WebApi/blob/master/sample/PSWebApi.OwinSample/CmdArgumentResolver.cs) in the sample project)*. The PSWebApi library only offers two common methods for escaping and quoting:
231+
- CmdArgumentsBuilder.**QuoteExeArgument**
232+
For most Microsoft C/C++/C# console applications (follow the rules described in https://msdn.microsoft.com/en-us/library/17w5ykft.aspx or https://msdn.microsoft.com/en-us/library/a1y7w461.aspx):
233+
1. A string will remain unchanged if it can be interpreted (by [CommandLineToArgvW](https://msdn.microsoft.com/en-us/library/windows/desktop/bb776391.aspx)) as a single argument;
232234
For examples,
233235
`3.14`
234236
`A_string_without_white_space`
235237
`"A string already surrounded by double quotation marks"`
236238
`/name_without_white_space:"value with spaces"`
237-
- Otherwise, if a string would be interpreted as multiple broken arguments or a string contains any unclosed double-quotation mark *(non-literal)*, then the original string will be surrounded by an extra pair of double-quotation marks at the outermost layer, and all the originally nested quotes will be escaped using the `\"` as **literal** double-quotation marks.
239+
2. Otherwise, if a string would be interpreted as multiple broken arguments or a string contains any unclosed double-quotation mark *(non-literal)*, then the original string will be surrounded by an extra pair of double-quotation marks at the outermost layer, and all the originally nested quotes will be escaped using the `\"` as **literal** double-quotation marks.
238240
For examples,
239-
`She's 5'5" tall.`
240-
will be encoded/escaped as
241-
`"She's 5'5\" tall."`
241+
`She's 5'5" tall.`
242+
will be encoded/escaped as
243+
`"She's 5'5\" tall."`
242244
243-
A test batch [test-args.bat](https://github.com/DataBooster/PS-WebApi/tree/master/sample/PSWebApi.OwinSample/scripts-root/bat-scripts) *(shipped with the sample project [PSWebApi.OwinSample](https://github.com/DataBooster/PS-WebApi/releases))* can be used to give it a try.
245+
A test batch [test-args.exe](https://github.com/DataBooster/PS-WebApi/tree/master/sample/Test-Args) *(build release mode to `..\PSWebApi.OwinSample\scripts-root\exe-apps\`)* can be used to give it a try.
246+
247+
- CmdArgumentsBuilder.**QuoteBatArgument**
248+
For batch files:
249+
1. A string will remain unchanged if it can be interpreted (by Windows command-line parser *CMD.EXE*) as a single argument;
250+
2. Otherwise, if a string would be interpreted as multiple broken arguments or a string contains any unclosed double-quotation mark (non-literal), then the original string will be surrounded by an extra pair of double-quotation marks at the outermost layer, and each originally nested double-quotation mark will be escaped as double double-quotation marks.
251+
For examples,
252+
`She's 5'5" tall.`
253+
will be encoded/escaped as
254+
`"She's 5'5"" tall."`
244255
245256
## Conventions
246257
#### HTTP status of PowerShell response

sample/PSWebApi.OwinSample/CmdArgumentResolver.cs

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
using DataBooster.PSWebApi;
1+
using System;
2+
using System.Net.Http;
3+
using Newtonsoft.Json.Linq;
4+
using DataBooster.PSWebApi;
25

36
namespace PSWebApi.OwinSample
47
{
@@ -11,7 +14,7 @@ public CmdArgumentResolver(string fileExtension)
1114
_fileExtension = (fileExtension == null) ? string.Empty : fileExtension.Trim().ToUpperInvariant();
1215
}
1316

14-
public string Quote(string rawArg, bool forceQuote = false)
17+
public virtual string Quote(string rawArg, bool forceQuote = false)
1518
{
1619
if (string.IsNullOrWhiteSpace(rawArg))
1720
return "\"" + (rawArg ?? string.Empty) + "\"";
@@ -23,5 +26,15 @@ public string Quote(string rawArg, bool forceQuote = false)
2326
default: return rawArg;
2427
}
2528
}
29+
30+
public string GatherInputArguments(HttpRequestMessage request, JToken argumentsFromBody, bool forceQuote)
31+
{
32+
if (request == null)
33+
throw new ArgumentNullException("request");
34+
35+
CmdArgumentsBuilder argsBuilder = new CmdArgumentsBuilder();
36+
37+
return argsBuilder.AddFromQueryString(request).Add(argumentsFromBody).ToString((string arg) => Quote(arg, forceQuote));
38+
}
2639
}
2740
}

sample/PSWebApi.OwinSample/Controllers/PSWebApiController.cs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,8 @@ public HttpResponseMessage InvokePS(string script, Dictionary<string, object> pa
2222
public HttpResponseMessage InvokeCMD(string script, JToken argumentsFromBody)
2323
{
2424
string physicalFullPath = script.LocalFullPath();
25-
CmdArgumentResolver cmdArgumentResolver = new CmdArgumentResolver(Path.GetExtension(physicalFullPath));
26-
string allArguments = this.Request.BuildCmdArguments(argumentsFromBody, (string arg) =>
27-
cmdArgumentResolver.Quote(arg, ConfigHelper.CmdForceArgumentQuote));
25+
CmdArgumentResolver argResolver = new CmdArgumentResolver(Path.GetExtension(physicalFullPath));
26+
string allArguments = argResolver.GatherInputArguments(this.Request, argumentsFromBody, ConfigHelper.CmdForceArgumentQuote);
2827

2928
return this.InvokeCmd(physicalFullPath, allArguments, ConfigHelper.CmdTimeoutSeconds);
3029
}

sample/PSWebApi.OwinSample/PSWebApi.OwinSample.csproj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,8 @@
4040
<WarningLevel>4</WarningLevel>
4141
</PropertyGroup>
4242
<ItemGroup>
43-
<Reference Include="DataBooster.PSWebApi, Version=1.0.13.0, Culture=neutral, PublicKeyToken=ee1eb06d9feeb8dc, processorArchitecture=MSIL">
44-
<HintPath>..\..\packages\DataBooster.PSWebApi.1.0.13-alpha\lib\net45\DataBooster.PSWebApi.dll</HintPath>
43+
<Reference Include="DataBooster.PSWebApi, Version=1.1.0.0, Culture=neutral, PublicKeyToken=ee1eb06d9feeb8dc, processorArchitecture=MSIL">
44+
<HintPath>..\..\packages\DataBooster.PSWebApi.1.1.0-alpha\lib\net45\DataBooster.PSWebApi.dll</HintPath>
4545
<Private>True</Private>
4646
</Reference>
4747
<Reference Include="Microsoft.CSharp" />

sample/PSWebApi.OwinSample/Properties/AssemblyInfo.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,5 +31,5 @@
3131
//
3232
// You can specify all the values or you can default the Revision and Build Numbers
3333
// by using the '*' as shown below:
34-
[assembly: AssemblyVersion("1.0.13.0")]
35-
[assembly: AssemblyFileVersion("1.0.13.0")]
34+
[assembly: AssemblyVersion("1.1.0.0")]
35+
[assembly: AssemblyFileVersion("1.1.0.0")]

sample/PSWebApi.OwinSample/packages.config

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<?xml version="1.0" encoding="utf-8"?>
22
<packages>
3-
<package id="DataBooster.PSWebApi" version="1.0.13-alpha" targetFramework="net45" />
3+
<package id="DataBooster.PSWebApi" version="1.1.0-alpha" targetFramework="net45" />
44
<package id="Microsoft.AspNet.Cors" version="5.2.3" targetFramework="net45" />
55
<package id="Microsoft.AspNet.WebApi.Client" version="5.2.3" targetFramework="net45" />
66
<package id="Microsoft.AspNet.WebApi.Core" version="5.2.3" targetFramework="net45" />

src/DataBooster.PSWebApi/PSControllerExtensions.cmd.cs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,22 +14,22 @@ namespace DataBooster.PSWebApi
1414
{
1515
public static partial class PSControllerExtensions
1616
{
17-
public static string BuildCmdArguments(this HttpRequestMessage request, IEnumerable<string> argsFromBody, Func<string, string> escapeArgument)
17+
public static string BuildCmdArguments(this HttpRequestMessage request, IEnumerable<string> argsFromBody, Func<string, string> funcQuoteArgument)
1818
{
1919
CmdArgumentsBuilder argsBuilder = new CmdArgumentsBuilder();
20-
return argsBuilder.AddFromQueryString(request).Add(argsFromBody).ToString(escapeArgument);
20+
return argsBuilder.AddFromQueryString(request).Add(argsFromBody).ToString(funcQuoteArgument);
2121
}
2222

23-
public static string BuildCmdArguments(this HttpRequestMessage request, IEnumerable<KeyValuePair<string, object>> argsFromBody, Func<string, string> escapeArgument)
23+
public static string BuildCmdArguments(this HttpRequestMessage request, IEnumerable<KeyValuePair<string, object>> argsFromBody, Func<string, string> funcQuoteArgument)
2424
{
2525
CmdArgumentsBuilder argsBuilder = new CmdArgumentsBuilder();
26-
return argsBuilder.AddFromQueryString(request).Add(argsFromBody).ToString(escapeArgument);
26+
return argsBuilder.AddFromQueryString(request).Add(argsFromBody).ToString(funcQuoteArgument);
2727
}
2828

29-
public static string BuildCmdArguments(this HttpRequestMessage request, JToken argsFromBody, Func<string, string> escapeArgument)
29+
public static string BuildCmdArguments(this HttpRequestMessage request, JToken argsFromBody, Func<string, string> funcQuoteArgument)
3030
{
3131
CmdArgumentsBuilder argsBuilder = new CmdArgumentsBuilder();
32-
return argsBuilder.AddFromQueryString(request).Add(argsFromBody).ToString(escapeArgument);
32+
return argsBuilder.AddFromQueryString(request).Add(argsFromBody).ToString(funcQuoteArgument);
3333
}
3434

3535
public static HttpResponseMessage InvokeCmd(this ApiController apiController, string scriptPath, string arguments, int timeoutSeconds = Timeout.Infinite)

src/DataBooster.PSWebApi/Properties/AssemblyInfo.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,6 @@
3232
// You can specify all the values or you can default the Build and Revision Numbers
3333
// by using the '*' as shown below:
3434
// [assembly: AssemblyVersion("1.0.*")]
35-
[assembly: AssemblyVersion("1.0.13.0")]
36-
[assembly: AssemblyFileVersion("1.0.13.0")]
37-
[assembly: AssemblyInformationalVersion("1.0.13-alpha")]
35+
[assembly: AssemblyVersion("1.1.0.0")]
36+
[assembly: AssemblyFileVersion("1.1.0.0")]
37+
[assembly: AssemblyInformationalVersion("1.1.0-alpha")]

0 commit comments

Comments
 (0)