I am trying to run a python script as a process to which I pass a couple of parameters and then read standard output. I have a little console app and a dummy script that works fine, but when I do the same thing in my WebApi project, Standard Output is always blank, and I cannot figure out why. My code follows:
Console App
class Program
{
private static string Foo(string inputString)
{
string result = String.Empty;
ProcessStartInfo start = new ProcessStartInfo();
start.FileName = "python";
start.Arguments = string.Format(" {0} {1} {2}", #"*path*\runner.py", #"*path*\test2.py", inputString);
start.UseShellExecute = false;
start.RedirectStandardOutput = true;
using (Process process = Process.Start(start))
{
using (StreamReader reader = process.StandardOutput)
{
result = reader.ReadToEnd();
}
}
return result;
}
static void Main(string[] args)
{
var result = Foo("flibble");
Console.Write(result);
Console.ReadKey();
}
}
runner.py (for the console app)
import sys, imp
test = imp.load_source('test',sys.argv[1])
result = test.hello(sys.argv[2])
test2.py (from the console app)
import sys
def hello(inputString):
sys.stdout.write(inputString)
return
That is the end of what I have that works, now onto the code where the issue is:
ApiEndpoint
[HttpPost]
public IHttpActionResult TestEndpoint()
{
string postedJson = ReadRawBuffer().Result;
if (postedJson == null) throw new ArgumentException("Request is null");
var result = _pythonOperations.Foo(postedJson);
// Deal with result
return Ok();
}
_pythonOperations.Foo()
public string Foo(string inputString)
{
string result;
var start = new ProcessStartInfo
{
FileName = _pathToPythonExecutable,
Arguments = string.Format(" {0} {1} {2}", _pathToPythonRunnerScript, _pathToPythonFooScript, inputString),
UseShellExecute = false,
RedirectStandardOutput = true
};
using (Process process = Process.Start(start))
{
using (StreamReader reader = process.StandardOutput)
{
result = reader.ReadToEnd();
}
}
return result;
}
pythonRunnerScript
import sys, imp
module = imp.load_source('foo', sys.argv[1])
module.Foo(sys.argv[2])
Foo script
import sys
def Foo(inputString)
outputString = "output"
sys.stdout.write(outputString)
return
This is quite possibly one of the longest posts I have made, so thanks for taking the time to read it, and hopefully I can get a solution to this.
Cheers
Turns out the format I was passing in was wrong. I was using Postman REST Api client, and pasting the huge amounts of data into their request content window truncated it, leaving me with half a line. Once this was sorted, everything ran through ok.
Related
I am trying to fetch the printers associated with my computer inside an ASP.NET Core 5 app using the System.Management.Automation and Microsoft.PowerShell.SDK nuget packages.
PowerShell ps = PowerShell.Create();
ps.AddScript("Get-Printer");
ps.Invoke();
However, when I try this, I get the following error
The 'Get-Printer' command was found in the module 'PrintManagement', but the module could not be loaded. For more information, run 'Import-Module PrintManagement'
I tried Import-Module PrintManagement, but nothing is being imported and I get no messages. I also tried setting the execution policy to unrestricted, but it still didn't work.
I tried the code in a .NET 5 application console and it worked perfectly fine so I guess the issue narrows down to ASP.NET Core 5.
Any help would be greatly appreciated!
I have checked the issue, and a good link about the Powershell.cs. And It missing something related SecureString. Then I change the code like below.
And It works fine, you could custom the output format in the controller. Here is my test result and code.
public IActionResult cmd()
{
var script = "Powershell Get-Printer";
string errors;
IEnumerable<PSObject> output;
var success = PowershellTools.RunPowerShellScript(script, out output, out errors);
List<string> response = new List<string>();
foreach (var item in output)
{
response.Add(item.ToString());
}
return Ok(response);
}
PowershellTools.cs
using System.Diagnostics;
using System.Management.Automation;
using System.Management.Automation.Runspaces;
using System.Security;
namespace Tools
{
public static class PowershellTools
{
public static bool RunPowerShellScript(string script, out IEnumerable<PSObject> output, out string errors)
{
return RunPowerShellScriptInternal(script, out output, out errors, null);
}
public static bool RunPowerShellScriptRemote(string script, string computer, string username, string password, out IEnumerable<PSObject> output, out string errors)
{
output = Enumerable.Empty<PSObject>();
var secured = new SecureString();
foreach (char letter in password)
{
secured.AppendChar(letter);
}
secured.MakeReadOnly();
var credentials = new PSCredential(username, secured);
var connectionInfo = new WSManConnectionInfo(false, computer, 5985, "/wsman", "http://schemas.microsoft.com/powershell/Microsoft.PowerShell", credentials);
var runspace = RunspaceFactory.CreateRunspace(connectionInfo);
try
{
runspace.Open();
}
catch (Exception e)
{
errors = e.Message;
return false;
}
return RunPowerShellScriptInternal(script, out output, out errors, runspace);
}
public static bool RunPowerShellScriptInternal(string script, out IEnumerable<PSObject> output, out string errors, Runspace runspace)
{
output = Enumerable.Empty<PSObject>();
using (var ps = PowerShell.Create())
{
ps.Runspace = runspace;
ps.AddScript(script);
try
{
output = ps.Invoke();
}
catch (Exception e)
{
Trace.TraceError("Error occurred in PowerShell script: " + e);
errors = e.Message;
return false;
}
if (ps.Streams.Error.Count > 0)
{
errors = String.Join(Environment.NewLine, ps.Streams.Error.Select(e => e.ToString()));
return false;
}
errors = String.Empty;
return true;
}
}
}
}
We have a REST endpoint installed as a windows service on a server. This server will be used for test automation. The idea is make a POST request to the endpoint and that endpoint will kick off cypress tests based off the parameters specified in the POST url.
I have an existing powershell script that will do this. Ideally I'd like to call that script and fill in the needed params.
No matter what I do the request returns OK without actually running any of the commands. Is there a way to use a POST request to execute a cmds or a powershell script?? Code below(Note: I'm trying 2 different methods here neither work):
namespace CNet.TestRunner
{
public class AddTestRun : CNetApiEndpoint
{
[HttpPost("Cypress/{testName}/{serverName}")]
public CineNetResult TestRun(string testName, string serverName)
{
string test = testName + ".js";
string testPath = $#"..\EndToEndSpecs\cypress\integration\{test}";
var path = "%PROGRAMFILES%";
var resolvedPath = Environment.ExpandEnvironmentVariables(path);
string cmd = $#"{resolvedPath}\PowerShell\7\pwsh.exe "" '..\EndToEnd.ps1' '-TestPath {testPath}' '-HostToTest {serverName}' """;
Process P = Process.Start($#"{resolvedPath}\PowerShell\7\pwsh.exe", $#""" '..\EndToEnd.ps1' '-TestPath {testPath}' '-HostToTest {serverName}'""");
P.WaitForExit();
int result = P.ExitCode;
string resStr = result.ToString();
var model = new ResponseModel
{
Message = resStr,
DataPassed = $"{testName}|{serverName}"
};
var proc1 = new ProcessStartInfo();
string anyCommand = "yarn install";
proc1.UseShellExecute = true;
proc1.WorkingDirectory = #"C:\Windows\System32";
proc1.FileName = #"C:\Windows\System32\cmd.exe";
proc1.Verb = "runas";
proc1.Arguments = "/c " + anyCommand;
proc1.WindowStyle = ProcessWindowStyle.Hidden;
Process.Start(proc1);
return Ok(model);
}
}
}
I can get a list of installed applications by using this PowerShell command:
Get-ItemProperty HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\* | Select-Object DisplayName
in C#, it doesn't return the exact list like I see in PowerShell, I need it to show exactly the output from PowerShell.
It shows a totally different list with other programs.
public void conf() {
process p1 = new Process();
ProcessStartInfo psi1 = new ProcessStartInfo("powershell", "Get-ItemProperty HKLM:\\Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\* | Select-Object DisplayName");
psi1.RedirectStandardOutput = true;
psi1.CreateNoWindow = true;
p1.StartInfo = psi1;
p1.StartInfo.UseShellExecute = false;
p1.StartInfo.Verb = "runas";
p1.Start();
string output = p1.StandardOutput.ReadToEnd();
Console.WriteLine(output);
p1.WaitForExit(400);
}
What am I doing wrong?
Thanks.
If you are talking about content - if you force C# program to run as x64 in Configuration Manager, you'll get the same output. And by default (Any CPU) it was reading from and x86 registry key. Or if you will run Powershell x86, you'll get the same result, as your original C# program
If you query also HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\* key and merge results you'll get the whole list. Make sure your program is x64 in that case
There is some mess with spaces in stdout from PowerShell, so I just removed it.
static void Main(string[] args)
{
var lines = GetSoft("Get-ItemProperty HKLM:\\Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\*")
.Union(GetSoft("Get-ItemProperty HKLM:\\Software\\Wow6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\*"))
.Distinct()
.ToList();
lines.Sort();
foreach (var line in lines)
{
Console.WriteLine(line);
}
Console.WriteLine(lines.Count);
Console.ReadLine();
}
public static IEnumerable<string> GetSoft(string key)
{
Process p1 = new Process();
ProcessStartInfo psi1 = new ProcessStartInfo("powershell",
key + " | Select-Object DisplayName")
{
RedirectStandardOutput = true,
CreateNoWindow = true
};
p1.StartInfo = psi1;
p1.StartInfo.UseShellExecute = false;
p1.StartInfo.Verb = "runas";
p1.Start();
var output = p1.StandardOutput.ReadToEnd();
var result= output.Split('\r', '\n').Select(s => s.Trim()).Where(s => !String.IsNullOrWhiteSpace(s));
p1.WaitForExit(400);
return result;
}
}
The problem comes in the looping. It seems to perform the comparison between the wmi qfe list and the KB list. The issue I run into is taking and searching the contents of the .msu file name list for the KB and then installing it. I have made a similar program in python and was mimicking a similar construct in the C# code. Any guidance on how to make this function properly would be greatly appreciated. The code is as follows:
static void Server08r2Patches()
{
var IE9 = from a in IAVA.Worksheet<IE9>("IE9") select a;
var Server08r2 = from a in IAVA.Worksheet<Server08r2>("Server08r2") select a;
var KBlist = Server08r2.Select(s=>new {KB = s.KB}).ToList();
var ExeList = Server08r2.Select(s=>new {Executable = s.Executable}).ToList();
string path = (Path.GetDirectoryName(Assembly.GetEntryAssembly().Location)+#"\x64\");
foreach (var item in KBlist)
{
if (OsPatches.Contains(item.KB))
{
Console.WriteLine(item.KB+" is already installed.");
}
else
{
Console.WriteLine(item.KB+" will now be installed.");
foreach (var inst in ExeList)
{
do
{
var kb_inst = new ProcessStartInfo()
{
CreateNoWindow = true,
UseShellExecute = true,
FileName =path+inst.Executable,
WindowStyle = ProcessWindowStyle.Hidden,
Arguments = (#" /quiet /norestart"),
};
try
{
Process update = new Process();
update.StartInfo = kb_inst;
update.Start();
update.WaitForExit();
Console.WriteLine(inst.Executable+" was successfully installed");
}
catch (Exception Ex)
{
Console.WriteLine(Ex.Message+" "+inst.Executable);
}
}
while (inst.Executable.Contains(item.KB) == true);
}
}
}
}
I need to get the revision number from TFS, if i run tf.exe from a Process the process halts. If i run the same command from command promts it works?
int revision;
var repo = "path to repo"
var psi = new ProcessStartInfo("cmd", #"/c ""C:\Program Files\Microsoft Visual Studio 10.0\Common7\IDE\tf.exe"" properties $/MYProject -recursive /version:W")
{
UseShellExecute = false,
ErrorDialog = false,
CreateNoWindow = false,
WorkingDirectory = repo,
RedirectStandardOutput = true,
RedirectStandardError = true
};
using (var p = Process.Start(psi))
{
p.WaitForExit();
if (p.ExitCode != 0)
{
using (var standardError = p.StandardError)
{
Console.WriteLine(standardError.ReadToEnd());
}
}
else
{
using (var standardOutput = p.StandardOutput)
{
revision = int.Parse(standardOutput.ReadToEnd());
}
}
}
edit:
I did this, works, should I go with it?
public int GetLatestChangeSet(string url, string project)
{
var server = new TeamFoundationServer(new Uri(url));
var version = server.GetService(typeof(VersionControlServer)) as VersionControlServer;
var items = version.GetItems(string.Format(#"$\{0}", project), RecursionType.Full);
return items.Items.Max(i => i.ChangesetId);
}
you better use the below namespace which contains all you need to achieve that
Microsoft.TeamFoundation.VersionControl.Client
//this is just an example
using Microsoft.TeamFoundation.Client;
using Microsoft.TeamFoundation.VersionControl.Client;
TfsTeamProjectCollection tpc = new TfsTeamProjectCollection(new Uri("http://myserver:8080/"));
VersionControlServer sourceControl = tpc.GetService<VersionControlServer>();
return sourceControl.GetLatestChangesetId();
http://msdn.microsoft.com/en-us/library/ms228232(v=vs.80)
The error occurs because your StandardOutput stream buffer is full and thus blocks. To read standard input/output it's recommended to subscribe to the OutputDataReceived event. Alternatively, spin up another thread to constantly read the data from the StandardOutput stream.
See the example on the OutputDataReceived event docs for a complete code sample.
A better solution would be to use the TFS API as suggested by Massimiliano Peluso. But this is the reason for your approach failing.
I ended up with this solution which uses the local workspace revision
public class ReadTfsRevisionTask : Task
{
public override bool Execute()
{
try
{
ChangesetId = GetLatestChangeSet(Server, Project);
return true;
}
catch
{
return false;
}
}
private int GetLatestChangeSet(string url, string project)
{
project = string.Format(#"$/{0}", project);
var server = new TeamFoundationServer(new Uri(url));
var version = server.GetService<VersionControlServer>();
var workspace = version.QueryWorkspaces(null, WindowsIdentity.GetCurrent().Name, System.Environment.MachineName).First();
var folder = workspace.Folders.First(f => f.ServerItem == project);
return workspace.GetLocalVersions(new[] { new ItemSpec(folder.LocalItem, RecursionType.Full) }, false)
.SelectMany(lv => lv.Select(l => l.Version)).Max();
}
[Required]
public string Server { get; set; }
[Required]
public string Project { get; set; }
[Output]
public int ChangesetId { get; set; }
}