I just load a PDF file in a WinForm using WebBrowser control. I have a PDF byte array and I used to write this in temporary folder of client machine when the user clicks on print preview in my application.
After preview when the user closes the form I need to delete the file. Before deleting I need to close the instance of Adobe Reader that was opened for preview.
I tried getting a list of processes using
Process[] processes = Process.GetProcessesByName("AcroRd32");
I can kill the process by this means and delete the file from temporary folder. Here comes the actual problem. Suppose the user already opened a PDF file for his own purpose then by finding the running instances and by closing them I will close his reader too.
Is there any way to find the instance of Adobe reader which is opened by my application. I need to check for Adobe reader only. Not any other.
UPDATE
I have opened adbobe reader manually which has filename or MainWindowTitle as Magazine.pdf first.Then i opened adobe reader by webbrowser control in my application.But it has MainWindowTitle as Empty.Also there shoul be two intance of adobe reader but in taskmanager it shows four instance and of them only one has MainWindowTitle as Magazine.pdf
See the below images for reference
Check the MainWindowTitle of each Acrobat process - the one you want will begin with the name of the temp file.
You might take a look at the Process's MainWindowHandle property.
I don't open the PDF using WebBrowser, but I have a similar problem to yours when I use Process.MainWindowTitle(). It seems that it only returns a value different from "" for the last opened instance of Acrobat.
The C# code I use to look for all processes of Acrobat is the following:
Process[] myProcess = Process.GetProcessesByName("AcroRd32");
for (int i = 0; i < procesoProg.Length; i++)
{
if (myProcess[i].MainWindowTitle.Contains("MyTitle"))
{
myProcess[i].CloseMainWindow();
Thread.Sleep(300);
}
}
I tried using Process.Kill() instead of Process.MainWindow(), but it closes all instances of Acrobat, not only the one I want.
If you only want to close the document you opened from your application, you can store the process when you launch the PDF, and use CloseMainWindow() when you want to close it:
private static Process myProcess = null;
public void OpenPDF()
{
myProcess = new Process();
myProcess.StartInfo.FileName = "AcroRd32.exe";
myProcess.StartInfo.Arguments = " /n /A \"pagemode=bookmarks&nameddest=" + strND + "\" \"" + strPath + "\"";
myProcess.Start();
}
public void ClosePDF()
{
// If there is
if (myProcess!= null)
{
myProcess.CloseMainWindow();
Thread.Sleep(500);
}
}
I hope this code can help you.
If you want to delete a PDF file opened in WebBrowser control you have to use this code:
WebBrowser.Navigate(YourPdfPath)
...
WebBrowser.Navigate("about:blank")
Application.DoEvents()
Dim millisecondsTimeout = 5000
While True
Try
File.Delete(YourPdfPath)
Exit While
Catch ex As IOException
If millisecondsTimeout < 0 Then Exit Sub
millisecondsTimeout -= 100
Thread.Sleep(100)
End Try
End While
UPDATE:
I found an extension method to kill a children process of a process:
<Extension>
Public Sub KillChildren(process As Process, Optional processNameToKill As String = "", Optional recursive As Boolean = False)
Static ProcessIDStack As List(Of Integer) = New List(Of Integer)
If ProcessIDStack.Contains(process.Id) Then
Exit Sub
Else
ProcessIDStack.Add(process.Id)
End If
Dim searcher = New ManagementObjectSearcher("Select * From Win32_Process Where ParentProcessID=" & process.Id) 'MLHIDE
Dim moc As ManagementObjectCollection = searcher.Get
For Each mo As ManagementObject In moc
Try
Dim ChildProcess = process.GetProcessById(Convert.ToInt32(mo("ProcessID"))) 'MLHIDE
If recursive Then
ChildProcess.KillChildren(processNameToKill, recursive)
End If
If (Not ChildProcess.HasExited) AndAlso (processNameToKill = String.Empty OrElse ChildProcess.ProcessName = processNameToKill) Then
ChildProcess.Kill()
ChildProcess.WaitForExit(1000)
End If
Catch ex As ArgumentException
' process already exited
Catch ex As InvalidOperationException
' process already exited
Catch ex As ComponentModel.Win32Exception
' process already exited
End Try
Next
ProcessIDStack.Remove(process.Id)
End Sub
...
WebBrowser.Navigate("about:blank")
Process.GetCurrentProcess.KillChildren("AcroRd32", True)
Application.DoEvents()
...
I'm searching for a better way to check if a file is not in use avoiding to sleep inside a loop (Andrew Barber correctly says that is not a good code...) but I didn't found anything else, sorry...
(Thanks to Andrew Barber)
Related
How would I go about starting a new process without it being the child of the calling process.
Example:
Main Program (Caller.exe)
process.start("file.exe")
Image:
If the spawning process (parent) ends before the spawned process (child) does, then the parent-child chain is broken. To make use of this, you'd have to use an intermediate stub-process like so:
Caller.exe → Stub.exe → File.exe.
Here Stub.exe is simple launcher program that ends just after starting File.exe.
If you start a process, then you'll be its parent.
Maybe you could try to start your process from cmd.exe instead, so cmd.exe will be the parent.
Process proc = Process.Start(new ProcessStartInfo { Arguments = "/C explorer", FileName = "cmd", WindowStyle = ProcessWindowStyle.Hidden });
I have been trying to start a updater process which deletes the files of the calling process and replaces them with new ones. By setting UseShellExecute = true, I was able to circumvent the spawned process from exiting when the calling process exited.
This is inside a .Net Core 3.0 application using WPF.
var startInfo = new ProcessStartInfo("Updater.exe");
startInfo.UseShellExecute = true;
Process.Start(startInfo);
Environment.Exit(0);
This runs new process without parent:
System.Diagnostics.ProcessStartInfo psi = new System.Diagnostics.ProcessStartInfo();
psi.FileName = #"cmd";
psi.Arguments = "/C start notepad.exe";
psi.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden;
System.Diagnostics.Process.Start(psi);
The documentation of Process.Start(string fileName) says
a new process that’s started alongside already running instances
of the same process will be independent
and it says
Starting a process by specifying its file name is similar to
typing the information in the Run dialog box of the Windows Start menu
which to me seems consistent with independent processes.
So according to the documentation, Process.Start should do what you desire.
System.Diagnostics.Process.Start() method calls kernel32!CreateProcess() under the hood. When creating a process with kernel32!CreateProcess() you can specify a different parent by using a process attribute. Here is a function written in C++ that does just that - although I'm not sure how .Net supports such features.
bool CreateProcessWithParent(DWORD parentId, PWSTR commandline) {
auto hProcess = ::OpenProcess(PROCESS_CREATE_PROCESS, FALSE, parentId);
if (!hProcess)
return false;
SIZE_T size;
//
// call InitializeProcThreadAttributeList twice
// first, get required size
//
::InitializeProcThreadAttributeList(nullptr, 1, 0, &size);
//
// now allocate a buffer with the required size and call again
//
auto buffer = std::make_unique<BYTE[]>(size);
auto attributes = reinterpret_cast<PPROC_THREAD_ATTRIBUTE_LIST>(buffer.get());
::InitializeProcThreadAttributeList(attributes, 1, 0, &size);
//
// add the parent attribute
//
::UpdateProcThreadAttribute(attributes, 0,
PROC_THREAD_ATTRIBUTE_PARENT_PROCESS,
&hProcess, sizeof(hProcess), nullptr, nullptr);
STARTUPINFOEX si = { sizeof(si) };
//
// set the attribute list
//
si.lpAttributeList = attributes;
PROCESS_INFORMATION pi;
//
// create the process
//
BOOL created = ::CreateProcess(nullptr, commandline, nullptr, nullptr,
FALSE, EXTENDED_STARTUPINFO_PRESENT, nullptr, nullptr,
(STARTUPINFO*)&si, &pi);
//
// cleanup
//
::CloseHandle(hProcess);
::DeleteProcThreadAttributeList(attributes);
return created;
}
Source code taken from https://scorpiosoftware.net/2021/01/10/parent-process-vs-creator-process/
Here is the code that I'm now using. I thought that it may be useful to someone. It accepts one argument. The argument is a base64 encoded string that decodes to the path of the file that you would like to run.
Module Module1
Sub Main()
Dim CommandLineArgs As System.Collections.ObjectModel.ReadOnlyCollection(Of String) = My.Application.CommandLineArgs
If CommandLineArgs.Count = 1 Then
Try
Dim path As String = FromBase64(CommandLineArgs(0))
Diagnostics.Process.Start(path)
Catch
End Try
End
End If
End Sub
Function FromBase64(ByVal base64 As String) As String
Dim b As Byte() = Convert.FromBase64String(base64)
Return System.Text.Encoding.UTF8.GetString(b)
End Function
End Module
I didnt found a good (free) and simple Solution for printing PDFs (for e.g. from a "Hot"-Folder (FileSystemWatcher) on a Server) with Acrobat and close Acrobat Reader. So i wrote my own and i hope it will help someone.
(Yes, u can use a old free Foxit Reader Version, but we had too much trouble with it, sometimes stuck in Memory without printing)
The Point was, after printing, the file must be moved to a archive dir, but Adobe did not close. So i never knowed when its done, or wait 30+ Seconds and kill (not so fine if the Server needs longer and takes to much time).
Here my Solution, i run the Process and wait until one of the subprocesses of my Adobe Process shows the recent open Window.
Thanks to mtijn for his "Process Searcher" solution https://stackoverflow.com/a/7189381/480982
var prz = Process.Start("C:\\Program Files (x86)\\Adobe\\Acrobat Reader DC\\Reader\\AcroRd32.exe", "/h /t \"" + YOURPDFFILE + "\" \"" + YOURPRINTER + "\"");
bool loop = true;
while (loop)
{
//u can use Thread.Sleep(x) too;
prz.WaitForExit(500);
ManagementObjectSearcher searcher = new ManagementObjectSearcher(
"SELECT * " +
"FROM Win32_Process " +
"WHERE ParentProcessId=" + prz.Id);
ManagementObjectCollection collection = searcher.Get();
if (collection.Count > 0)
{
foreach (var item in collection)
{
UInt32 childProcessId = (UInt32)item["ProcessId"];
if ((int)childProcessId != Process.GetCurrentProcess().Id)
{
Process childProcess = Process.GetProcessById((int)childProcessId);
//If a File is open the Title begins with "Filename - Adobe ...", but after print/closing the recent window starts with "Adobe Acr..."
if(childProcess.MainWindowTitle.StartsWith("Adobe Acrobat"))
{
loop = false;
break;
}
}
}
}
}
//"Recent" Window found, lets kill the Process
prz.Kill();
// Now here u can move or Delete the pdf file
My program prints silently via the PDF reader Foxit Reader in a new process.
On occasion, my program attempts to print two PDFs at the same time, which causes one of them to fail to print.
Here is my code:
string filename = "file.pdf";
string fileDir1 = #"C:\Program Files (x86)\Foxit Software\Foxit Reader\FoxitReader.exe";
Process pdfProcess = new Process();
pdfProcess.StartInfo.FileName = fileDir1;
pdfProcess.StartInfo.Arguments = string.Format(#"/t {0} {1}", filename ,"pos-80");
pdfProcess.StartInfo.CreateNoWindow = true;
pdfProcess.StartInfo.WorkingDirectory = Path.GetDirectoryName(fileDir1);
pdfProcess.Start();
if (!pdfProcess.WaitForExit(2500))
{
pdfProcess.Kill();
}
Please help me resolve this issue.
Make sure you have Foxit open.
using System.Diagnostics;
List<Process> Processlist = Process.GetProcesses().ToList();
This gives you a list of currently running processes.
foreach(Process p in Processlist)
{
Console.WriteLine("Process " + p.Id + " is named '" + p.ProcessName + "'");
}
When running the above code, you should see the name of the Foxit process in your output window.
Alternatively, put a break-point on the foreach line and hover over the list to see all the names that way.
bool IsFoxitProcessRunning = false;
foreach(Process p in Processlist)
{
if(p.ProcessName == "Foxit process name here") //Replace with the name of the foxit process
{
IsFoxitProcessRunning = true;
}
}
Now, only start a new Foxit process if one isn't already running.
if(!IsFoxitProcessRunning)
{
//insert code to run next foxit process here.
}
Notes:
You may need to implement a queue to keep track of pdfs waiting to run.
You may also wish to alert IT support if a Foxit is waiting more 5 or 10 mins to run.
You could choose to make Processlist a class attribute, and periodically refresh Processlist using a Timer, by calling Processlist = Process.GetProcesses().ToList(); on the Tick event. every 30 seconds or so while a PDF is waiting to be printed.
I'm trying to run a local process using C# to call Powerpoint and convert a .pdf to a .ppt.
I made a standalone console app, in hopes of reproducing and isolating the problem.
Unfortunately, it works for the standalone version, but not with the integrated version.
When it doesn't work, it throws no exceptions. It just silently fails to create the .pdf file.
NEW:
I'm getting an error on the event log:
Microsoft PowerPoint
PowerPoint can't do this because a dialog box is open. Please close the dialog box to continue.
P1: 400205
P2: 15.0.4420.1017
I don't see any sort of dialog box when running the console commands, the standalone console application, or running the integrated web project on my local machine.
The /pt command is supposed to be silent, as per the official documentation.
I can set the Identity of the ApplicationPool the project is running under to the user that I log in as, and I no longer get the above error in the event log. However, I get no other errors (that I can tell are related) from the event log.
It still doesn't work, however. Powerpoint or PDFCreator still crashes, and doesn't create the .pdf.
I also tried to run my working console app by calling it as a Process from my integrated issue, and that didn't work either.
The working console application:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System;
using System.Diagnostics;
using System.Text;
namespace ConsoleApplication3
{
class Program
{
static void Main(string[] args)
{
var psi = new ProcessStartInfo();
psi.FileName = "\"C:\\Program Files\\Microsoft Office\\Office15\\POWERPNT.exe\"";
psi.Arguments = "/pt \"PDFCreator\" \"\" \"\" dd0e03ff-f386-4e65-b89d-72c7f1ee502d.pptx";
psi.WorkingDirectory = "C:\\Temp";
psi.CreateNoWindow = true;
psi.ErrorDialog = true;
psi.UseShellExecute = false;
psi.WindowStyle = ProcessWindowStyle.Hidden;
psi.RedirectStandardOutput = true;
psi.RedirectStandardInput = false;
psi.RedirectStandardError = true;
try
{
using (Process exeProcess = Process.Start(psi))
{
exeProcess.PriorityClass = ProcessPriorityClass.High;
var outString = new StringBuilder(100);
exeProcess.OutputDataReceived += (s, e) => outString.AppendLine(e.Data);
exeProcess.BeginOutputReadLine();
var errString = exeProcess.StandardError.ReadToEnd();
if (!string.IsNullOrEmpty(errString))
{
Console.WriteLine("errors reported 1");
}
}
}
catch (Exception ex)
{
ex.ToString();
Console.WriteLine("Errors reported 2 ");
Console.WriteLine(ex.ToString());
}
Console.WriteLine(psi.FileName);
Console.WriteLine(psi.Arguments);
Console.WriteLine(psi.WorkingDirectory);
}
}
}
It prints
"C:\Program Files\Microsoft Office\Office15\POWERPNT.exe"
/pt "PDFCreator" "" "" dd0e03ff-f386-4e65-b89d-72c7f1ee502d.pptx
C:\Temp
The not working integrated application:
using System;
using System.Diagnostics;
using System.Text;
namespace CT.Services.Helper
{
public static class ExecutableRunner
{
public static ExecutableResult RunExeNamed(string exeFilename, string commandLineArgs)
{
return RunExeNamed(exeFilename, commandLineArgs, null);
}
public static ExecutableResult RunExeNamed(string exeFilename, string commandLineArgs, string workingDirectory)
{
var result = new ExecutableResult { WasSuccessful = true };
var psi = new ProcessStartInfo();
psi.FileName = "\""+exeFilename+"\"";
psi.Arguments = commandLineArgs;
if(!string.IsNullOrEmpty(workingDirectory))
{
psi.WorkingDirectory = workingDirectory;
}
psi.CreateNoWindow = false;
psi.ErrorDialog = true;
psi.UseShellExecute = false;
psi.WindowStyle = ProcessWindowStyle.Hidden;
psi.RedirectStandardOutput = true;
psi.RedirectStandardInput = false;
psi.RedirectStandardError = true;
try
{
// Start the process with the info we specified.
// Call WaitForExit and then the using statement will close.
using (Process exeProcess = Process.Start(psi))
{
exeProcess.PriorityClass = ProcessPriorityClass.High;
var outString = new StringBuilder(100);
// use ansynchronous reading for at least one of the streams
// to avoid deadlock
exeProcess.OutputDataReceived += (s, e) => outString.AppendLine(e.Data);
exeProcess.BeginOutputReadLine();
// now read the StandardError stream to the end
// this will cause our main thread to wait for the
// stream to close (which is when ffmpeg quits)
var errString = exeProcess.StandardError.ReadToEnd();
if (!string.IsNullOrEmpty(errString))
{
result.WasSuccessful = false;
result.ErrorMessage = errString;
}
}
}
catch (Exception ex)
{
result.WasSuccessful = false;
result.ErrorMessage = ex.ToString();
}
Debug.WriteLine(psi.FileName);
Debug.WriteLine(psi.Arguments);
Debug.WriteLine(psi.WorkingDirectory);
return result;
}
}
public class ExecutableResult
{
public bool WasSuccessful { get; set; }
public string ErrorMessage { get; set; }
}
}
it prints
"C:\Program Files\Microsoft Office\Office15\POWERPNT.exe"
/pt "PDFCreator" "" "" dd0e03ff-f386-4e65-b89d-72c7f1ee502d.pptx
C:\Temp
I would think that one of the strings I'm using as the file path or console command would be wrong, so I had them printed to the console, but you can see that those aren't the issue.
Last try ;-)
You could use Interop, open the file and afterwards save it as pdf.
For this little example to work you need to reference the Microsoft.Office.Interop.PowerPoint assembly and the Microsoft.Office.Core namespace (found in the "Microsoft Office 14.0 Object Library" assembly which is located under the COM assemblies).
Little Example:
/// <summary>
/// Converts the specified source file and saves it as pdf with the
/// specified destination filename.
/// </summary>
/// <param name="sourceFilename">The filename of the file to be converted into a pdf.</param>
/// <param name="destinationFilename">The filename of the pdf.</param>
/// <exception cref="System.IO.FileNotFoundException">Is thrown if the specified source file does not exist.</exception>
/// <exception cref="System.Exception">Is thrown if something went wrong during the convertation process.</exception>
public static void SaveAsPDF(string sourceFilename, string destinationFilename)
{
if (!File.Exists(sourceFilename))
{
throw new FileNotFoundException(string.Format("The specified file {0} does not exist.", sourceFilename), sourceFilename);
}
try
{
Microsoft.Office.Interop.PowerPoint.Application app = new Microsoft.Office.Interop.PowerPoint.Application();
app.Presentations.Open(sourceFilename).SaveAs(destinationFilename, Microsoft.Office.Interop.PowerPoint.PpSaveAsFileType.ppSaveAsPDF);
app.Quit();
}
catch (Exception e)
{
throw new Exception(string.Format("Unable to convert {0} to {1}", sourceFilename, destinationFilename), e);
}
}
Could it be a problem with the permissions of the application pool? Maybe it does not have permissions to perform all the actions you want to - you could check that by executing your console application under the same context as the application pool is running.
Just another idea (not sure if it is a stupid one or not). As far as I remember powerpoint (at least since 2010) is able to save directly to pdf. For 2007 you can use a microsoft plugin called "Save as PDF" which can be found here 2007 Microsoft Office Add-in: Microsoft Save as PDF or XPS.
Using the provided Solution from Todd Main you could (theoretically) execute a macro at startup which could store the file directly as PDF (without printing and hopefully without dialog box).
Update:
I've tried it right now - problem is you have to store the macro in the presentation, therfore you have to use the pptm file format. Next you have to open the file once and activate the macro execution (is shown on the top of the editing screen). After that you can simply execute it from the command line like
POWERPNT.EXE /M Filename.pptm SaveAsPDF
The macro looks like this:
Sub SaveAsPDF()
Dim ap As Presentation: Set ap = ActivePresentation
ap.SaveAs ap.Path & "\" & ap.Name, ppSaveAsPDF
PowerPoint.Application.Quit
End Sub
The integrated version of the code is part of a web application
Provide access to the current working directory where this file is being created for the app pool user and verify it has Read/Write.
Check the Temp directory on the server for the app-pool process user and verify that the process has read/write access to the temp directory.
Edit Upon seeing the event log.
Asp.net should not be using any application which is a user interactive process. You need to find a different application to create these powerpoint slides that is asp.net friendly or determine what Powerpoint is trying to do when it opens a dialog.
Odds are you are seeing this problem because you've never actually opened up Powerpoint under the user that is running the application in the environment where it's failing.
This will be the user that the application pool is running under and, as I see you've mentioned to OmegaMan, that user is LocalSystem.
When LocalSystem tries to open Powerpoint, then Powerpoint will be doing that thing that all Office applications do where they complete their install the first time a new user runs them.
You are not seeing this installation box in your console app or on your local system because you're running those under your own account and you've previously opened PowerPoint.
As OmegaMan rightly says Powerpoint is meant to be an interactive process so it is probably going to cause trouble even if you're very very careful. You'll end up with instances of Powerpoint staying open after they're supposed to have closed and loads of instances building up in the
That said, if you really want to get this running then you either need to get the application pool running as a user that has opened Powerpoint interactively, or change your code to use impersonation at the point where you need to run Powerpoint and then impersonate a user that has used it interactively to do what you need.
How would I go about starting a new process without it being the child of the calling process.
Example:
Main Program (Caller.exe)
process.start("file.exe")
Image:
If the spawning process (parent) ends before the spawned process (child) does, then the parent-child chain is broken. To make use of this, you'd have to use an intermediate stub-process like so:
Caller.exe → Stub.exe → File.exe.
Here Stub.exe is simple launcher program that ends just after starting File.exe.
If you start a process, then you'll be its parent.
Maybe you could try to start your process from cmd.exe instead, so cmd.exe will be the parent.
Process proc = Process.Start(new ProcessStartInfo { Arguments = "/C explorer", FileName = "cmd", WindowStyle = ProcessWindowStyle.Hidden });
I have been trying to start a updater process which deletes the files of the calling process and replaces them with new ones. By setting UseShellExecute = true, I was able to circumvent the spawned process from exiting when the calling process exited.
This is inside a .Net Core 3.0 application using WPF.
var startInfo = new ProcessStartInfo("Updater.exe");
startInfo.UseShellExecute = true;
Process.Start(startInfo);
Environment.Exit(0);
This runs new process without parent:
System.Diagnostics.ProcessStartInfo psi = new System.Diagnostics.ProcessStartInfo();
psi.FileName = #"cmd";
psi.Arguments = "/C start notepad.exe";
psi.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden;
System.Diagnostics.Process.Start(psi);
The documentation of Process.Start(string fileName) says
a new process that’s started alongside already running instances
of the same process will be independent
and it says
Starting a process by specifying its file name is similar to
typing the information in the Run dialog box of the Windows Start menu
which to me seems consistent with independent processes.
So according to the documentation, Process.Start should do what you desire.
System.Diagnostics.Process.Start() method calls kernel32!CreateProcess() under the hood. When creating a process with kernel32!CreateProcess() you can specify a different parent by using a process attribute. Here is a function written in C++ that does just that - although I'm not sure how .Net supports such features.
bool CreateProcessWithParent(DWORD parentId, PWSTR commandline) {
auto hProcess = ::OpenProcess(PROCESS_CREATE_PROCESS, FALSE, parentId);
if (!hProcess)
return false;
SIZE_T size;
//
// call InitializeProcThreadAttributeList twice
// first, get required size
//
::InitializeProcThreadAttributeList(nullptr, 1, 0, &size);
//
// now allocate a buffer with the required size and call again
//
auto buffer = std::make_unique<BYTE[]>(size);
auto attributes = reinterpret_cast<PPROC_THREAD_ATTRIBUTE_LIST>(buffer.get());
::InitializeProcThreadAttributeList(attributes, 1, 0, &size);
//
// add the parent attribute
//
::UpdateProcThreadAttribute(attributes, 0,
PROC_THREAD_ATTRIBUTE_PARENT_PROCESS,
&hProcess, sizeof(hProcess), nullptr, nullptr);
STARTUPINFOEX si = { sizeof(si) };
//
// set the attribute list
//
si.lpAttributeList = attributes;
PROCESS_INFORMATION pi;
//
// create the process
//
BOOL created = ::CreateProcess(nullptr, commandline, nullptr, nullptr,
FALSE, EXTENDED_STARTUPINFO_PRESENT, nullptr, nullptr,
(STARTUPINFO*)&si, &pi);
//
// cleanup
//
::CloseHandle(hProcess);
::DeleteProcThreadAttributeList(attributes);
return created;
}
Source code taken from https://scorpiosoftware.net/2021/01/10/parent-process-vs-creator-process/
Here is the code that I'm now using. I thought that it may be useful to someone. It accepts one argument. The argument is a base64 encoded string that decodes to the path of the file that you would like to run.
Module Module1
Sub Main()
Dim CommandLineArgs As System.Collections.ObjectModel.ReadOnlyCollection(Of String) = My.Application.CommandLineArgs
If CommandLineArgs.Count = 1 Then
Try
Dim path As String = FromBase64(CommandLineArgs(0))
Diagnostics.Process.Start(path)
Catch
End Try
End
End If
End Sub
Function FromBase64(ByVal base64 As String) As String
Dim b As Byte() = Convert.FromBase64String(base64)
Return System.Text.Encoding.UTF8.GetString(b)
End Function
End Module