Powershell read the output of the process is already running - c#

I have a powershell script which runs an exe and get its output which is working fine, but the issue that i am facing is that if the process is already running then it is not able to capture the output.
Below is my code
$pinfo = New-Object System.Diagnostics.ProcessStartInfo
$pinfo.FileName = "E:\Program Files (x86)\Consul_Ent\Bin\consul.exe"
$pinfo.Arguments = "agent -config-dir E:\Consul_Ent\Configuration\Client"
$pinfo.UseShellExecute = $false
$pinfo.CreateNoWindow = $false
$pinfo.RedirectStandardInput = $true
$pinfo.RedirectStandardOutput = $true
$pinfo.RedirectStandardError = $true
$pinfo.EnvironmentVariables["GOMAXPROCS"] = (Get-WmiObject Win32_Processor | measure -p NumberOfLogicalProcessors -sum).Sum
$process = New-Object System.Diagnostics.Process
$process.StartInfo = $pinfo
[Void]$process.Start()
$process.BeginOutputReadLine()
$process.BeginErrorReadLine()
try
{
while (!$process.WaitForExit(1))
{
}
Write-Host "WaitForExit(1)"
}
finally
{
# give the thread gracefully shutdown
Start-Sleep -s 3
}
so as the process is not terminated yet and hence it is not able to read the output as it tries to create a new instance, i want it to create a new instance only if process is not running but if it is running i just want to capture the output.

Related

ExitCode always has null does not show the actual return value from exe

I am calling my exe from powershell script like shown below.
$file = $PSScriptRoot + "\executor.exe"
$code = (Start-Process -WindowStyle Hidden $file -Verb runAs -ArgumentList $Logfile).StandardOutput.ToString;
$nid = (Get-Process "executor.exe").id
Wait-Process -Id $nid
if ($code -eq 1) {
LogWrite "Execution succeeded"
} else
{
LogWrite "Execution Failed"
}
I have a int main function in my exe program which would return 1 on success, and 0 on failure.
When i try to get the ExitCode(using $LASTEXITCODE) from powershell script it always shows null(neither 1 nor 0), But my exe is returning 1 as expected.
How do i capture the return value of exe in powershell script?
You can use this:
$psi = New-Object System.Diagnostics.ProcessStartInfo
$psi.FileName = # path to your exe file
# additional options:
$psi.UseShellExecute = $false
$psi.CreateNoWindow = $false
$psi.WindowStyle = "Maximized"
$p = New-Object System.Diagnostics.Process
$p.StartInfo = $psi
$p.Start() | Out-Null # returns $true if the process started, $false otherwise
$p.WaitForExit()
# here's the exitcode
$exitCode = $p.ExitCode
Create the process start info, to specify the executable path, and additional options. It's important to use .WaitForExit() to wait until the process finishes.
What you have tried doesn't get the app exitcode, but what the application write to standard console, which, in your case, I assume is nothing. If you could modify the exe to write to the console, what you did would work.

powershell script unable to locate certain files when executed from a windows service

I have a powershell script which spits out file information for a given file. The script is executed in a process from a windows service like so:
Process p = new Process();
ProcessStartInfo s = new ProcessStartInfo();
s.FileName = "powershell.exe";
s.Arguments = "./script.ps1";
s.RedirectStandardOutput = true;
s.RedirectStandardError = true;
s.UseShellExecute = false;
s.CreateNoWindow = true;
p.StartInfo = s;
p.EnableRaisingEvents = true;
/* ... defined output handlers ... */
p.Start();
p.BeginOutputReadLine();
p.BeginErrorReadLine();
The powershell script is as follows:
function ChangeDir($dir)
{
try
{
echo ("Attempting to change directory: {0}" -f ($dir))
Set-Location -Path $dir -ErrorAction Stop
}
catch
{
echo $error[0].Exception
}
}
function OutputFileInfo($filePath)
{
try
{
echo ("Attempting to read file: {0}" -f ($filePath))
$file = #(Get-ChildItem $filePath -ErrorAction Stop)
for ($i = 0; $i -lt $file.Count; $i++)
{
echo ("{0},{1}" -f ($file[$i].Name, $file[$i].Length))
}
}
catch
{
echo $error[0].Exception
}
}
ChangeDir "/Windows/System32/drivers"
OutputFileInfo "tcpip.sys"
The output when running the powershell script from the command line is as I expect it to be:
Attempting to change directory: /Windows/System32/drivers
Attempting to read file: tcpip.sys
tcpip.sys,2773400
When the script executes via the windows service the output is this:
Attempting to change directory: /Windows/System32/drivers
Attempting to read file: tcpip.sys
Cannot find path 'C:\Windows\System32\drivers\tcpip.sys' because it does not exist.
For other files it works perfectly fine from the command line and the service. Could it have something to do with the service running the powershell script as SYSTEM which somehow doesn't have access to that file? Although if that were the case I would expect a permissions error instead of a file not found error.
Ok.... this was because system32/drivers is not accessible for 32bit applications that run on 64bit machines and I didn't realize I had my application set to the default in visual studio which I guess is 32bit. I changed it to 64bit and it works from the service now.

Detaching process from powershell hosted in C# application

We have a C# application that executes PowerShell scripts as an extension point, we don't have access to change this code but it is pretty much the following:
string command = #"C:\temp.ps1";
var fileName = "powershell";
var args = $"-ExecutionPolicy unrestricted . '{command}'";
var process = CreateProcess(fileName, args);
ExecuteProcess(ref process);
private Process CreateProcess(string fileName, string args)
{
return new Process
{
StartInfo =
{
FileName = fileName,
Arguments = args,
RedirectStandardError = true,
RedirectStandardOutput = true,
UseShellExecute = false
}
};
}
private int ExecuteProcess(ref Process proc)
{
proc.Start();
string text = string.Empty;
string text2 = string.Empty;
while (!proc.HasExited)
{
Thread.Sleep(1000);
text += proc.StandardOutput.ReadToEnd();
text2 += proc.StandardError.ReadToEnd();
}
proc.WaitForExit();
text2 += proc.StandardError.ReadToEnd();
text += proc.StandardOutput.ReadToEnd();
Console.WriteLine(text);
Console.WriteLine(text2);
return proc.ExitCode;
}
We have a PowerShell script that gets executed from this code that has a long-running process, to keep this simple we'll just use ping.exe with a -t argument.
ping -t google.com
We want to be able to fork the ping.exe process so that the c# application can resume execution as soon as possible and that the ping.exe in this example continues on it's merry way.
I've tried to run Start-Process inside the powershell script but this still just blocks the execution of the C# application until all the processes have fully executed (so it eventually runs to completion):
Start-Process ping -ArgumentList "-t","google.com" -RedirectStandardOutput ".\out.log" -RedirectStandardError ".\err.log"
I've also tried to run Start-Job and wrap the start process in a separate job, however, this seems to start the job but never completes
Start-Job -ScriptBlock { Start-Process ping -ArgumentList "-t","google.com" -RedirectStandardOutput ".\out.log" -RedirectStandardError ".\err.log" }
Is there any way to start a new process from within PowerShell and allow the C# application to continue executing?
I've kinda found a workaround - if I pass in -Verb Open to Start-Process it seems to resume execution to the C# application straight away. The only problem is that you can't redirect the standard out or error to files.
Start-Process ping -ArgumentList "-t","google.com" -Verb Open

Prevent Shutdown - C#, Powershell or CMD/Bat

I would like to ask how to prevent shutdown, when running a script or at least give a popup that will ask whenever or not you want to shutdown (like when you open a notepad and write a char, but doesn't save it and the click shutdown).
I have been creating scripts that runs installers silent, but some of them still seems to activate windows shutdown (this can happen if they are missing prerequisites).
Here is the code I use for the installation:
# --- Install ---
$fileExtension = (Get-ChildItem -path $installationFilePath).Extension
if(".msi" -eq $fileExtension)
{
[string[]]$Private:args = New-Object string[] 4
$args[0] = "/qn"
$args[1] = "TRANSFORM=1033.mst"
$args[2] = "REBOOT=Suppress"
$args[3] = "/l*v $errorLogPath"
$process = Start-Process $installationFilePath -ArgumentList $args -PassThru
}
if(".exe" -eq $fileExtension)
{
[string[]]$Private:args = New-Object string[] 2
$args[0] = '/v"' + "/qn TRANSFORM=1033.mst REBOOT=Suppress /l*v $errorLogPath" + '"'
$args[1] = "/s"
$process = Start-Process $installationFilePath -ArgumentList $args -PassThru
}
$processActive = $process
while ($processActive -ne $null)
{
Start-Sleep -Seconds 1
Write-Host '.' -NoNewline
$processActive = Get-Process -Id $processActive.Id -ErrorAction SilentlyContinue
}
I know this should be possible, but I have yet to find out how.
Here is an example of aborting shutdown after the install has finished:
Start-Process yourprogram.exe -Wait
shutdown /a
You could even loop the abort a few times to make sure you hit it.
for($i=0;$i -lt 5;$i++)
{
shutdown /a
sleep 1
}

Running powershell from process acts different then from PowerShell ISE

I've got PowerShell script that acts like expected if I run it from PowerShell ISE.
$ol = #()
$ProcessActive = Get-Process outlook -ErrorAction SilentlyContinue
if($ProcessActive -eq $null)
{
$ol = New-Object -comObject Outlook.Application
}
else
{
$ol = [Runtime.InteropServices.Marshal]::GetActiveObject("Outlook.Application")
}
$file = "c:\testfile.pdf"
$mail = $ol.CreateItem(0)
$Mail.Recipients.Add("test#test.nl")
$mail.Subject = "This is a subject"
$mail.HTMLBody = "<html><body><h3>Hello world</h3></body></html>"
$mail.Attachments.Add($file)
$inspector = $mail.GetInspector
$inspector.Display()
However ... if I start a process in C# to execute the script it will only work if the Outlook process is not running.
var filename = "script.ps1";
var fullname = path + filename;
if (System.IO.File.Exists(fullname))
{
ProcessStartInfo startInfo = new ProcessStartInfo();
startInfo.FileName = #"powershell.exe";
startInfo.Arguments = string.Format(#"& '{0}'", fullname);
startInfo.RedirectStandardOutput = true;
startInfo.RedirectStandardError = true;
startInfo.UseShellExecute = false;
startInfo.CreateNoWindow = true;
Process process = new Process();
process.StartInfo = startInfo;
process.Start();
process.WaitForExit();
System.IO.File.Delete(fullname);
}
The process eventually ends execution and the file is deleted in both cases (outlook running or not).
What do I need to change in order to let the script execute properly when started from a C# process (even if Outlook is running)?
Thanks in advance.
In order to answer what the difference was between script and process I've created a log of the process that was running the script. To create the log i've used
System.IO.File.WriteAllText(#"c:\log.txt",process.StandardError.ReadToEnd())
First the exception was: missing references. After fixing the missing reference by adding at the top of the powershell script :
Add-Type -Path 'Dir\To\Dll'
After that I received another error:
Exception calling "GetActiveObject" with "1" argument(s): "Operation unavailabl
e (Exception from HRESULT: 0x800401E3 (MK_E_UNAVAILABLE))"
I've read some articles and this has to do with that Outlook does not allow different users 'using' the running instance (current user vs admin-like current user running script). I'm now using the Microsoft.Office.Interop.Outlook dll to open a new email window and the console application that executes it can run as current user without the need of admin rights. This solved my problem: i'm now able to open a new email window even if outlook is already running.

Categories

Resources