How to keep a process alive after remote PowerShell script completion? - c#

I'm trying to start Zookeeper and Solr remotely via PowerShell scriptblock.
Then I see that the process is created in the remote machine (by checking the port 2181 of Zookeeper). And on the script completion it is being terminated.
How do I keep this alive even after the completion?
This code below stops the remote process on script completion. The script.ps1 does a lot of things that includes starting Zookeeper and Solr asJob.
int iRemotePort = 5985;
string strShellURI = #"http://schemas.microsoft.com/powershell/Microsoft.PowerShell";
string strAppName = #"/wsman";
WSManConnectionInfo ci = new WSManConnectionInfo(
false,
machineName,
iRemotePort,
strAppName,
strShellURI,
new PSCredential(userName, secure));
Runspace runspace = RunspaceFactory.CreateRunspace(ci);
runspace.Open();
using (PowerShell ps = PowerShell.Create())
{
session.Log(#"c:\temp\script.ps1 -serverID " + counter + " -copySolrConfig $" + status + " -currentHost \"" + machineName + "\" -IP_PORT_List \"" + String.Join(", ", machineWithPort) + "\"");
ps.Runspace = runspace;
ps.AddScript(#"c:\temp\script.ps1 -serverID " + counter + " -copySolrConfig $" + status + " -currentHost \"" + machineName + "\" -IP_PORT_List \"" + String.Join(", ", machineWithPort) + "\"");
var results = ps.Invoke();
foreach (var result in results)
{
session.Log(result.ToString());
}
}
runspace.Close();

So after a long try I came to a conclusion, Using power-shell script alive is not a appropriate way to keep zookeeper and solr running effectively. So moved it as a windows service and installed it on the remote machine via WiX.

Related

"sqlcmd.exe -S Instance USE ..." work in command line, but I can't move it to c # process

I am trying move this command to C# Process:
SQLCMD.EXE -S InstanceName
USE [master]
GO
CREATE DATABASE [Ek] ON
( FILENAME = N'C:\Program Files\Microsoft SQL
Server\MSSQL15.MSSQLSERVER\MSSQL\DATA\Ek_Primary.mdf' ),
( FILENAME = N'C:\Program Files\Microsoft SQL
Server\MSSQL15.MSSQLSERVER\MSSQL\DATA\Ek_Primary.ldf' )
FOR ATTACH ;
GO
EXIT
I have:
try
{
Process p = CreateProcess();
p.StartInfo.FileName = #"C:\Program Files\Microsoft SQL Server\Client SDK\ODBC\170\Tools\Binn\SQLCMD.EXE";
p.StartInfo.Arguments = "-S InstanceName" + "\n" +
"USE [master]" + "\n" +
"GO" + "\n" +
"CREATE DATABASE [Ek] ON" + "\n" +
"( FILENAME = N'C:\\Program Files\\Microsoft SQL Server\\MSSQL15.MSSQLSERVER\\MSSQL\\DATA\\Ek_Primary.mdf' )," + "\n" +
"( FILENAME = N'C:\\Program Files\\Microsoft SQL Server\\MSSQL15.MSSQLSERVER\\MSSQL\\DATA\\Ek_Primary.ldf' )" + "\n" +
"FOR ATTACH ;" + "\n" +
"GO" + "\n" +
"EXIT" + "\n";
Console.WriteLine(p.StartInfo.Arguments);
p.Start();
var output = p.StandardOutput.ReadToEnd();
var err = p.StandardError.ReadToEnd();
Console.WriteLine("O: " + output);
Console.WriteLine("E: " + err);
}
catch (Exception e) { Console.WriteLine(e.Message); ; }
It return err = Unexpected argument. Enter '-?' for help.
I was trying set FileName on cmd.exe and move path to Arguments. But it waits forever for a response and does not exit p.StandardOutput.ReadToEnd ();
I was trying send each line of code individually, but also without success.
And I trying with /C on start p.StartInfo.Argument but it does not change anything.
Thanks to Selvin suggestion. I made it by:
string sqlConnectionString = #"Integrated Security=SSPI;Persist Security Info=False;Data Source=InstanceName";
string script = File.ReadAllText(#"../../sql.sql");
Microsoft.Data.SqlClient.SqlConnection conn = new Microsoft.Data.SqlClient.SqlConnection(sqlConnectionString);
Server server = new Server(new ServerConnection(conn));
server.ConnectionContext.ExecuteNonQuery(script);
where in sql.sql I added all my sql line.

System.Automation.Runspaces Error in .NET Core project

I've tried to run the a piece of code in a Console Application - C# (.NET Framework 4.5 project) and it works. But when I tried to migrate it to ASP.NET Core 2.0, the code would return an error(as shown below).
using System.Management.Automation.Runspaces;
public void Powershell()
{
Runspace runspace = RunspaceFactory.CreateRunspace();
runspace.Open();
Pipeline pipeline = runspace.CreatePipeline();
pipeline.Commands.AddScript("Import-Module AzureAD -Force;");
pipeline.Commands.AddScript("$password = ConvertTo-SecureString " + "\"Accenture01\"" + " -AsPlainText -Force");
pipeline.Commands.AddScript("$Cred = New-Object System.Management.Automation.PSCredential (" + "\"TestID01#eso.dev.accenture.com\"" + ", $password)");
pipeline.Commands.AddScript("Connect-AzureAD -Credential $Cred");
pipeline.Commands.AddScript("Get-AzureADApplication -Filter " + "\"DisplayName eq " + "\'TestApp\'" + "\"");
var result = pipeline.Invoke();
}
The error is
"'System.PlatformNotSupportedException: 'ReflectionOnly loading is
not supported on this platform.'"
Any ideas on this?
Use Powershell.Create() instead of Pipeline, Invoke the Import-Module before you continue, use Add-Command instead of AddScript
Try this: (after opening the runspace, e.g runspace.Open();)
using (var powershell = PowerShell.Create())
{
powershell.Runspace = runspace;
powershell.Commands.AddCommand("Import-Module").AddArgument("AzureAD");
powershell.Invoke();
powershell.Commands.Clear();
powershell.AddScript("$password = ConvertTo-SecureString " + "\"Accenture01\"" + " -AsPlainText -Force");
powershell.AddScript("$Cred = New-Object System.Management.Automation.PSCredential (" + "\"TestID01#eso.dev.accenture.com\"" + ", $password)");
powershell.AddScript("Connect-AzureAD -Credential $Cred");
powershell.AddScript("Get-AzureADApplication -Filter " + "\"DisplayName eq " + "\'TestApp\'" + "\"");
powershell.Invoke();
var results = powershell.Invoke();
}

Execute a bash script in Unity Cloud Build

My requirement is to execute a bash script "PostProcessShellScript.sh" in PostProcessBuild. Unity build runs fine, but build fails in Unity Cloud Build.
Below is my code :
public static void OnPostProcessBuild(BuildTarget target, string pathToBuiltProject) {
.....
string scriptPath = Application.dataPath + "/Plugins/Android/Editor/PostProcessShellScript.sh";
//Runs fine on Local Unity build
//RunInShell("C:\\Program Files\\bash.exe","/" + scriptPath + " " + " " + "/" + apkPath + "/"+ "/" + apkName, false);
RunInShell("open","/" + scriptPath + " " + " " + "/" + apkPath + "/"+ "/" + apkName, false);
}
public static void RunInShell(string file, string args, bool waitForExit = true) {
System.Diagnostics.Process ppScriptProcess = new System.Diagnostics.Process();
ppScriptProcess.StartInfo.WindowStyle = System.Diagnostics.ProcessWindowStyle.Maximized;
ppScriptProcess.StartInfo.FileName = file;
ppScriptProcess.StartInfo.Arguments = args;
ppScriptProcess.StartInfo.UseShellExecute = false;
ppScriptProcess.StartInfo.CreateNoWindow = false;
ppScriptProcess.StartInfo.RedirectStandardOutput = true;
ppScriptProcess.StartInfo.RedirectStandardError = true;
ppScriptProcess.Start ()
}
Error details on UCB :
! Unity player export failed!
! build of 'default_-android' failed. compile failed
Publishing build 22 of surbhijain87/roll-a-ball-game for target
'default_-android'...
publishing finished successfully.
done.
Build step 'Execute shell' marked build as failure
postbuildstatus finished successfully. Finished: FAILURE
too implicit.
I am using the shell scripting to resolve the issues.
UCB running on mono and shell scripting looks like the way to go.
Your resolution: convert the code to a shell script and attach on the UCB config from the UI

Check MSI installation status

I have a MSI file which I am copying to client machine using WMI Remote Connection. After Copying it to some location , I install it and update status as 'Installation Completed'. But the problem I face is the process of sending status completed is taking place so instantly after the code below that even if it fails the Status would be Completed.
inParams["CommandLine"] = "msiexec.exe /i " + #"""" + #"c:\windows\temp\" + tempFolderName + #"\Agent.msi" + #"""" + " " + String.Format("/q HEARTBEATSERVERIP={0} CONSOLESERVICEURL={1} HEARTBEATSERVERPORT={2} DOMAINACCOUNT={3} DOMAINACCOUNTPASSWORD={4}", this.heartbeatServerIp, this.consoleServiceUrl, this.heartbeatServerPort, this.userName, this.password);
The above line initiates the installation process and the below code determines how I process the whole thing
File.WriteAllText(remoteCopy, cmdFileBuilder.ToString());
Process scriptProc = new Process();
scriptProc.StartInfo.FileName = "cscript";
scriptProc.StartInfo.WorkingDirectory = programDataPath; //<---very important exe location
scriptProc.StartInfo.Arguments = "//B //Nologo remotecopy.vbs";
scriptProc.StartInfo.WindowStyle = ProcessWindowStyle.Hidden; //prevent console window from popping up
scriptProc.Start();
scriptProc.WaitForExit(); // <-- Optional if you want program running until your script exit
scriptProc.Close();
var agentMsiFileName = "\\" + ipAddress + #"\admin$\Temp\" + tempFolderName + #"\Agent.msi";
//remote copy completed...
//remote copy completed...
ProcessStartInfo info = new ProcessStartInfo();
///Start remote execution
///
try
{
var wmiProcess = new ManagementClass(scope, new ManagementPath("Win32_Process"), new ObjectGetOptions());
ManagementBaseObject inParams = wmiProcess.GetMethodParameters("Create");
inParams["CommandLine"] = "msiexec.exe /i " + #"""" + #"c:\windows\temp\" + tempFolderName + #"\Agent.msi" + #"""" + " " + String.Format("/q HEARTBEATSERVERIP={0} CONSOLESERVICEURL={1} HEARTBEATSERVERPORT={2} DOMAINACCOUNT={3} DOMAINACCOUNTPASSWORD={4}", this.heartbeatServerIp, this.consoleServiceUrl, this.heartbeatServerPort, this.userName, this.password);
ManagementBaseObject outParams = wmiProcess.InvokeMethod("Create", inParams, null);
LogUtilities.Info("Creation of the process returned: " + outParams["returnValue"]);
LogUtilities.Info("Process ID: " + outParams["processId"]);
LogUtilities.Info("Installation completed for : " + discoveredMachineId + " " + ipAddress);
SendAgentStatus(discoveredMachineId, "Completed"); //Installation Completed
I know that we can check for the status codes of installer which I used in the code and it returns '0' instantly without waiting for the completion, its for creation of the process completion.
ManagementBaseObject outParams = wmiProcess.InvokeMethod("Create", inParams, null);
LogUtilities.Info("Creation of the process returned: " + outParams["returnValue"]);
Also I could cross-check the registry with
var checkKey = Registry.LocalMachine.OpenSubKey(#"Software\Microsoft\Windows\CurrentVersion\Uninstall\{Product-code}");
but it takes atleast 1-2 minutes or even more than that depending on the system configurations. So, is there a way we can figure this out to perfection?
I want to wait till the installation gets complete and then send the status Completed.

how to set a timeout when executing Powershell & Powercli commands inside asp.net mvc

I am working on an asp.net mvc web application, and i have the following code which define a loop over a list of servers and execute PowerCli commands inside my asp.net mvc for each server:-
//Start Loop
var shell = PowerShell.Create();
var shell2 = PowerShell.Create();
var shell3 = PowerShell.Create();
string PsCmd = "add-pssnapin VMware.VimAutomation.Core; $vCenterServer = '" + vCenterName + "';$vCenterAdmin = '" + vCenterUsername + "' ;$vCenterPassword = '" + vCenterPassword + "';" + System.Environment.NewLine;
PsCmd = PsCmd + "$VIServer = Connect-VIServer -Server $vCenterServer -User $vCenterAdmin -Password $vCenterPassword;" + System.Environment.NewLine;
PsCmd = PsCmd + "Get-VMHost " + System.Environment.NewLine;
string PsCmd2 = "add-pssnapin VMware.VimAutomation.Core; $vCenterServer = '" + vCenterName + "';$vCenterAdmin = '" + vCenterUsername + "' ;$vCenterPassword = '" + vCenterPassword + "';" + System.Environment.NewLine;
PsCmd2 = PsCmd2 + "$VIServer = Connect-VIServer -Server $vCenterServer -User $vCenterAdmin -Password $vCenterPassword;" + System.Environment.NewLine;
PsCmd2 = PsCmd2 + " Get-VMHost " + vCenterName + "| Get-VMHostNetworkAdapter -VMKernel" + System.Environment.NewLine;
shell.Commands.AddScript(PsCmd);
shell2.Commands.AddScript(PsCmd2);
dynamic results = shell.Invoke();
dynamic results2 = shell2.Invoke();
// end of loop
but i have noted that sometimes the shell commands will hang and the execution never ends,, so can i define a timeout behavior ,, so that after 5 minutes to skip the commands if no results were returned ...
You will have to roll your own timeout command. Below is code I wrote based on a MSDN Blog entry by Keith Babinec - Executing PowerShell scripts from C#. I wrote the sample in Console Application for demonstration purposes only. I find it easier to see what is happen. You can convert it to Asp.Net application by removing the Console Output and other adjustments.
Here is Program.cs
using System;
using System.Management.Automation;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
string script = "Write-Host \"Testing lopping...\"" + Environment.NewLine
+ "for ($i=1; $i -le 5; $i++)" + Environment.NewLine
+ "{" + Environment.NewLine
+ "Write-Output $i" + Environment.NewLine
+ "Start-Sleep -s 3" + Environment.NewLine
+ "}" + Environment.NewLine
+ "Write-Host \"Done!\"" + Environment.NewLine;
PowerShell shell = PowerShell.Create();
shell.AddScript(script);
PowerShellHelper helper = new PowerShellHelper(shell);
try
{
// the script above should take 15 seconds to execute
// do timeout of 10 minutes
helper.ExecuteAsynchronously(new TimeSpan(0, 10, 0));
// do a really short timeout - 2 seconds
helper.ExecuteAsynchronously(new TimeSpan(0, 0, 2));
}
catch(TimeoutException te)
{
Console.WriteLine("\n\nScript took long!");
}
Console.WriteLine("Demo Finish");
Console.ReadLine();
}
}
}
And here is PowerShellHelper.cs
using System;
using System.Management.Automation;
using System.Threading;
// This code was build from MSDN Blogs entry by Keith Babinec
// http://blogs.msdn.com/b/kebab/archive/2014/04/28/executing-powershell-scripts-from-c.aspx
namespace ConsoleApplication1
{
class PowerShellHelper
{
private PowerShell shell_;
public PowerShellHelper(PowerShell shell)
{
shell_ = shell;
}
public void ExecuteAsynchronously(TimeSpan timeout)
{
// prepare a new collection to store output stream objects
PSDataCollection<PSObject> outputCollection = new PSDataCollection<PSObject>();
outputCollection.DataAdded += outputCollection_DataAdded;
// begin invoke execution on the pipeline
// use this overload to specify an output stream buffer
IAsyncResult result = shell_.BeginInvoke<PSObject, PSObject>(null, outputCollection);
// start the timer
DateTime startTime = DateTime.Now;
// do something else until execution has completed.
// this could be sleep/wait, or perhaps some other work
while (result.IsCompleted == false)
{
Console.WriteLine("Waiting for pipeline to finish...");
Thread.Sleep(100);
// we check on our timeout here
TimeSpan elasped = DateTime.Now.Subtract(startTime);
if (elasped > timeout)
{
// we can do a few things here, I like to throw exception
throw new TimeoutException("Powershell script taking too long");
}
}
Console.WriteLine("Execution has stopped. The pipeline state: " + shell_.InvocationStateInfo.State);
foreach (PSObject outputItem in outputCollection)
{
//TODO: handle/process the output items if required
if (outputItem != null)
{
Console.WriteLine(outputItem.BaseObject.ToString());
}
}
}
/// <summary>
/// Event handler for when data is added to the output stream.
/// </summary>
/// <param name="sender">Contains the complete PSDataCollection of all output items.</param>
/// <param name="e">Contains the index ID of the added collection item and the ID of the PowerShell instance this event belongs to.</param>
private void outputCollection_DataAdded(object sender, DataAddedEventArgs e)
{
// do something when an object is written to the output stream
Console.WriteLine("Object added to output.");
}
}
}
I prefer this short construction:
using (PowerShell ps = PowerShell.Create())
{
ps.AddScript(script);
var psAsyncResult = ps.BeginInvoke();
if (psAsyncResult.AsyncWaitHandle.WaitOne(timeoutMilliseconds))
{
// Execution finished
var results = ps.EndInvoke(psAsyncResult);
}
else
{
// Execution terminated by timeout
Console.WriteLine($"Unable to complete running powershell script within {timeoutMilliseconds} milliseconds");
}
}
Powershell invocation with timeout
There is a much shorter (thus less error-prone) solution:
using (PowerShell ps = PowerShell.Create())
{
ps.AddScript(script);
Task invocationTask = ps.InvokeAsync();
try
{
// Ensure the task is waited for the timeout duration.
// As documentation says if the timeout is reached then the task is faulted
if (!invocationTask.Wait(timeout))
{
isTimeouted = true;
}
}
finally
{
// task may not be completed here
// and disposal of not completed task will raise an exception
if (invocationTask != null && invocationTask.IsCompleted)
{
invocationTask.Dispose();
}
}
}

Categories