Pushing up initial 1.0 version.
This commit is contained in:
parent
d181726257
commit
bb7d5ec2bc
263
.gitignore
vendored
Normal file
263
.gitignore
vendored
Normal file
@ -0,0 +1,263 @@
|
||||
## Ignore Visual Studio temporary files, build results, and
|
||||
## files generated by popular Visual Studio add-ons.
|
||||
|
||||
# User-specific files
|
||||
*.suo
|
||||
*.user
|
||||
*.userosscache
|
||||
*.sln.docstates
|
||||
|
||||
# User-specific files (MonoDevelop/Xamarin Studio)
|
||||
*.userprefs
|
||||
|
||||
# Build results
|
||||
[Dd]ebug/
|
||||
[Dd]ebugPublic/
|
||||
[Rr]elease/
|
||||
[Rr]eleases/
|
||||
x64/
|
||||
x86/
|
||||
bld/
|
||||
[Bb]in/
|
||||
[Oo]bj/
|
||||
[Ll]og/
|
||||
|
||||
# Visual Studio 2015 cache/options directory
|
||||
.vs/
|
||||
# Uncomment if you have tasks that create the project's static files in wwwroot
|
||||
#wwwroot/
|
||||
|
||||
# MSTest test Results
|
||||
[Tt]est[Rr]esult*/
|
||||
[Bb]uild[Ll]og.*
|
||||
|
||||
# NUNIT
|
||||
*.VisualState.xml
|
||||
TestResult.xml
|
||||
|
||||
# Build Results of an ATL Project
|
||||
[Dd]ebugPS/
|
||||
[Rr]eleasePS/
|
||||
dlldata.c
|
||||
|
||||
# DNX
|
||||
project.lock.json
|
||||
project.fragment.lock.json
|
||||
|
||||
*_i.c
|
||||
*_p.c
|
||||
*_i.h
|
||||
*.ilk
|
||||
*.meta
|
||||
*.obj
|
||||
*.pch
|
||||
*.pdb
|
||||
*.pgc
|
||||
*.pgd
|
||||
*.rsp
|
||||
*.sbr
|
||||
*.tlb
|
||||
*.tli
|
||||
*.tlh
|
||||
*.tmp
|
||||
*.tmp_proj
|
||||
*.log
|
||||
*.vspscc
|
||||
*.vssscc
|
||||
.builds
|
||||
*.pidb
|
||||
*.svclog
|
||||
*.scc
|
||||
|
||||
# Chutzpah Test files
|
||||
_Chutzpah*
|
||||
|
||||
# Visual C++ cache files
|
||||
ipch/
|
||||
*.aps
|
||||
*.ncb
|
||||
*.opendb
|
||||
*.opensdf
|
||||
*.sdf
|
||||
*.cachefile
|
||||
*.VC.db
|
||||
*.VC.VC.opendb
|
||||
|
||||
# Visual Studio profiler
|
||||
*.psess
|
||||
*.vsp
|
||||
*.vspx
|
||||
*.sap
|
||||
|
||||
# TFS 2012 Local Workspace
|
||||
$tf/
|
||||
|
||||
# Guidance Automation Toolkit
|
||||
*.gpState
|
||||
|
||||
# ReSharper is a .NET coding add-in
|
||||
_ReSharper*/
|
||||
*.[Rr]e[Ss]harper
|
||||
*.DotSettings.user
|
||||
|
||||
# JustCode is a .NET coding add-in
|
||||
.JustCode
|
||||
|
||||
# TeamCity is a build add-in
|
||||
_TeamCity*
|
||||
|
||||
# DotCover is a Code Coverage Tool
|
||||
*.dotCover
|
||||
|
||||
# NCrunch
|
||||
_NCrunch_*
|
||||
.*crunch*.local.xml
|
||||
nCrunchTemp_*
|
||||
|
||||
# MightyMoose
|
||||
*.mm.*
|
||||
AutoTest.Net/
|
||||
|
||||
# Web workbench (sass)
|
||||
.sass-cache/
|
||||
|
||||
# Installshield output folder
|
||||
[Ee]xpress/
|
||||
|
||||
# DocProject is a documentation generator add-in
|
||||
DocProject/buildhelp/
|
||||
DocProject/Help/*.HxT
|
||||
DocProject/Help/*.HxC
|
||||
DocProject/Help/*.hhc
|
||||
DocProject/Help/*.hhk
|
||||
DocProject/Help/*.hhp
|
||||
DocProject/Help/Html2
|
||||
DocProject/Help/html
|
||||
|
||||
# Click-Once directory
|
||||
publish/
|
||||
|
||||
# Publish Web Output
|
||||
*.[Pp]ublish.xml
|
||||
*.azurePubxml
|
||||
# TODO: Comment the next line if you want to checkin your web deploy settings
|
||||
# but database connection strings (with potential passwords) will be unencrypted
|
||||
#*.pubxml
|
||||
*.publishproj
|
||||
|
||||
# Microsoft Azure Web App publish settings. Comment the next line if you want to
|
||||
# checkin your Azure Web App publish settings, but sensitive information contained
|
||||
# in these scripts will be unencrypted
|
||||
PublishScripts/
|
||||
|
||||
# NuGet Packages
|
||||
*.nupkg
|
||||
# The packages folder can be ignored because of Package Restore
|
||||
**/packages/*
|
||||
# except build/, which is used as an MSBuild target.
|
||||
!**/packages/build/
|
||||
# Uncomment if necessary however generally it will be regenerated when needed
|
||||
#!**/packages/repositories.config
|
||||
# NuGet v3's project.json files produces more ignoreable files
|
||||
*.nuget.props
|
||||
*.nuget.targets
|
||||
|
||||
# Microsoft Azure Build Output
|
||||
csx/
|
||||
*.build.csdef
|
||||
|
||||
# Microsoft Azure Emulator
|
||||
ecf/
|
||||
rcf/
|
||||
|
||||
# Windows Store app package directories and files
|
||||
AppPackages/
|
||||
BundleArtifacts/
|
||||
Package.StoreAssociation.xml
|
||||
_pkginfo.txt
|
||||
|
||||
# Visual Studio cache files
|
||||
# files ending in .cache can be ignored
|
||||
*.[Cc]ache
|
||||
# but keep track of directories ending in .cache
|
||||
!*.[Cc]ache/
|
||||
|
||||
# Others
|
||||
ClientBin/
|
||||
~$*
|
||||
*~
|
||||
*.dbmdl
|
||||
*.dbproj.schemaview
|
||||
*.jfm
|
||||
*.pfx
|
||||
*.publishsettings
|
||||
node_modules/
|
||||
orleans.codegen.cs
|
||||
|
||||
# Since there are multiple workflows, uncomment next line to ignore bower_components
|
||||
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
|
||||
#bower_components/
|
||||
|
||||
# RIA/Silverlight projects
|
||||
Generated_Code/
|
||||
|
||||
# Backup & report files from converting an old project file
|
||||
# to a newer Visual Studio version. Backup files are not needed,
|
||||
# because we have git ;-)
|
||||
_UpgradeReport_Files/
|
||||
Backup*/
|
||||
UpgradeLog*.XML
|
||||
UpgradeLog*.htm
|
||||
|
||||
# SQL Server files
|
||||
*.mdf
|
||||
*.ldf
|
||||
|
||||
# Business Intelligence projects
|
||||
*.rdl.data
|
||||
*.bim.layout
|
||||
*.bim_*.settings
|
||||
|
||||
# Microsoft Fakes
|
||||
FakesAssemblies/
|
||||
|
||||
# GhostDoc plugin setting file
|
||||
*.GhostDoc.xml
|
||||
|
||||
# Node.js Tools for Visual Studio
|
||||
.ntvs_analysis.dat
|
||||
|
||||
# Visual Studio 6 build log
|
||||
*.plg
|
||||
|
||||
# Visual Studio 6 workspace options file
|
||||
*.opt
|
||||
|
||||
# Visual Studio LightSwitch build output
|
||||
**/*.HTMLClient/GeneratedArtifacts
|
||||
**/*.DesktopClient/GeneratedArtifacts
|
||||
**/*.DesktopClient/ModelManifest.xml
|
||||
**/*.Server/GeneratedArtifacts
|
||||
**/*.Server/ModelManifest.xml
|
||||
_Pvt_Extensions
|
||||
|
||||
# Paket dependency manager
|
||||
.paket/paket.exe
|
||||
paket-files/
|
||||
|
||||
# FAKE - F# Make
|
||||
.fake/
|
||||
|
||||
# JetBrains Rider
|
||||
.idea/
|
||||
*.sln.iml
|
||||
|
||||
# CodeRush
|
||||
.cr/
|
||||
|
||||
# Python Tools for Visual Studio (PTVS)
|
||||
__pycache__/
|
||||
*.pyc
|
||||
|
||||
#Mac Store files
|
||||
.DS_Store
|
BIN
Logo_Symbol_Black_Outline.png
Normal file
BIN
Logo_Symbol_Black_Outline.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 20 KiB |
16
Process.Net.Example/Process.Net.Example.csproj
Normal file
16
Process.Net.Example/Process.Net.Example.csproj
Normal file
@ -0,0 +1,16 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<ImplicitUsings>disable</ImplicitUsings>
|
||||
<Nullable>disable</Nullable>
|
||||
<AssemblyName>MontoyaTech.Process.Net.Example</AssemblyName>
|
||||
<RootNamespace>MontoyaTech.Process.Net.Example</RootNamespace>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Process.Net\Process.Net.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
27
Process.Net.Example/Program.cs
Normal file
27
Process.Net.Example/Program.cs
Normal file
@ -0,0 +1,27 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace MontoyaTech.Process.Net.Example
|
||||
{
|
||||
public class Program
|
||||
{
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
Console.WriteLine("Starting process");
|
||||
|
||||
var startInfo = new ProcessStartInfo()
|
||||
{
|
||||
FileName = "cmd.exe",
|
||||
RedirectStandardInput = true,
|
||||
};
|
||||
|
||||
var process = WinProcess.Create(startInfo, creationFlags: WinProcess.CreationFlags.CREATE_NEW_CONSOLE);
|
||||
|
||||
process.StandardInput.WriteLine("This is a test");
|
||||
process.StandardInput.WriteLine("Does this work?");
|
||||
|
||||
Console.WriteLine("Done.");
|
||||
Console.ReadLine();
|
||||
}
|
||||
}
|
||||
}
|
28
Process.Net.Tests/Process.Net.Tests.csproj
Normal file
28
Process.Net.Tests/Process.Net.Tests.csproj
Normal file
@ -0,0 +1,28 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<ImplicitUsings>disable</ImplicitUsings>
|
||||
<Nullable>disable</Nullable>
|
||||
<IsPackable>false</IsPackable>
|
||||
<IsTestProject>true</IsTestProject>
|
||||
<AssemblyName>MontoyaTech.Process.Net.Tests</AssemblyName>
|
||||
<RootNamespace>MontoyaTech.Process.Net.Tests</RootNamespace>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="coverlet.collector" Version="3.2.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.5.0" />
|
||||
<PackageReference Include="xunit" Version="2.4.2" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Process.Net\Process.Net.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Using Include="Xunit" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
37
Process.Net.sln
Normal file
37
Process.Net.sln
Normal file
@ -0,0 +1,37 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.11.35303.130
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Process.Net", "Process.Net\Process.Net.csproj", "{4A548DAB-3399-41B6-A980-840F314A2775}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Process.Net.Tests", "Process.Net.Tests\Process.Net.Tests.csproj", "{911717BA-B0B6-400A-B66B-FE1E1A517498}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Process.Net.Example", "Process.Net.Example\Process.Net.Example.csproj", "{BB1F617E-FC1F-454D-A508-BD06E1ABB8AB}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{4A548DAB-3399-41B6-A980-840F314A2775}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{4A548DAB-3399-41B6-A980-840F314A2775}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{4A548DAB-3399-41B6-A980-840F314A2775}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{4A548DAB-3399-41B6-A980-840F314A2775}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{911717BA-B0B6-400A-B66B-FE1E1A517498}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{911717BA-B0B6-400A-B66B-FE1E1A517498}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{911717BA-B0B6-400A-B66B-FE1E1A517498}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{911717BA-B0B6-400A-B66B-FE1E1A517498}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{BB1F617E-FC1F-454D-A508-BD06E1ABB8AB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{BB1F617E-FC1F-454D-A508-BD06E1ABB8AB}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{BB1F617E-FC1F-454D-A508-BD06E1ABB8AB}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{BB1F617E-FC1F-454D-A508-BD06E1ABB8AB}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {344F0622-F335-46A5-8622-81650D985D94}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
29
Process.Net/Process.Net.csproj
Normal file
29
Process.Net/Process.Net.csproj
Normal file
@ -0,0 +1,29 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<ImplicitUsings>disable</ImplicitUsings>
|
||||
<Nullable>disable</Nullable>
|
||||
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
|
||||
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
|
||||
<PackageIcon>Logo_Symbol_Black_Outline.png</PackageIcon>
|
||||
<RepositoryUrl>https://code.montoyatech.com/MontoyaTech/Process.Net</RepositoryUrl>
|
||||
<Authors>MontoyaTech</Authors>
|
||||
<Company>MontoyaTech</Company>
|
||||
<Copyright>MontoyaTech 2024</Copyright>
|
||||
<PackageProjectUrl>https://code.montoyatech.com/MontoyaTech/Process.Net</PackageProjectUrl>
|
||||
<PackageTags>MontoyaTech;Process.Net</PackageTags>
|
||||
<AssemblyName>MontoyaTech.Process.Net</AssemblyName>
|
||||
<RootNamespace>MontoyaTech.Process.Net</RootNamespace>
|
||||
<Version>1.0.0</Version>
|
||||
<Company>MontoyaTech</Company>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="..\Logo_Symbol_Black_Outline.png">
|
||||
<Pack>True</Pack>
|
||||
<PackagePath>\</PackagePath>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
561
Process.Net/WinProcess.cs
Normal file
561
Process.Net/WinProcess.cs
Normal file
@ -0,0 +1,561 @@
|
||||
using Microsoft.Win32.SafeHandles;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
namespace MontoyaTech.Process.Net
|
||||
{
|
||||
/// <summary>
|
||||
/// A class to help create Windows Processes using the Kernel32 CreateProcess function.
|
||||
/// </summary>
|
||||
public class WinProcess
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal class ProcessInformation
|
||||
{
|
||||
public IntPtr hProcess = IntPtr.Zero;
|
||||
public IntPtr hThread = IntPtr.Zero;
|
||||
public int dwProcessId;
|
||||
public int dwThreadId;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The outline of the different startup flags that can be used.
|
||||
/// See here for more info: https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/ns-processthreadsapi-startupinfoa
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum StartupFlags : int
|
||||
{
|
||||
/// <summary>
|
||||
/// No flags are set.
|
||||
/// </summary>
|
||||
NONE = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Indicates that the cursor is in feedback mode for two seconds after CreateProcess is called. The Working in Background cursor is displayed (see the Pointers tab in the Mouse control panel utility).
|
||||
/// If during those two seconds the process makes the first GUI call, the system gives five more seconds to the process. If during those five seconds the process shows a window, the system gives five more seconds to the process to finish drawing the window.
|
||||
/// The system turns the feedback cursor off after the first call to GetMessage, regardless of whether the process is drawing.
|
||||
/// </summary>
|
||||
STARTF_FORCEONFEEDBACK = 0x00000040,
|
||||
|
||||
/// <summary>
|
||||
/// Indicates that the feedback cursor is forced off while the process is starting. The Normal Select cursor is displayed.
|
||||
/// </summary>
|
||||
STARTF_FORCEOFFFEEDBACK = 0x00000080,
|
||||
|
||||
/// <summary>
|
||||
/// Indicates that any windows created by the process cannot be pinned on the taskbar.
|
||||
// This flag must be combined with STARTF_TITLEISAPPID.
|
||||
/// </summary>
|
||||
STARTF_PREVENTPINNING = 0x00002000,
|
||||
|
||||
/// <summary>
|
||||
/// Indicates that the process should be run in full-screen mode, rather than in windowed mode.
|
||||
/// This flag is only valid for console applications running on an x86 computer.
|
||||
/// </summary>
|
||||
STARTF_RUNFULLSCREEN = 0x00000020,
|
||||
|
||||
/// <summary>
|
||||
/// The lpTitle member contains an AppUserModelID. This identifier controls how the taskbar and Start menu present the application, and enables it to be associated with the correct shortcuts and Jump Lists. Generally, applications will use the SetCurrentProcessExplicitAppUserModelID and GetCurrentProcessExplicitAppUserModelID functions instead of setting this flag. For more information, see Application User Model IDs.
|
||||
/// If STARTF_PREVENTPINNING is used, application windows cannot be pinned on the taskbar. The use of any AppUserModelID-related window properties by the application overrides this setting for that window only.
|
||||
/// This flag cannot be used with STARTF_TITLEISLINKNAME.
|
||||
/// </summary>
|
||||
STARTF_TITLEISAPPID = 0x00001000,
|
||||
|
||||
/// <summary>
|
||||
/// The lpTitle member contains the path of the shortcut file (.lnk) that the user invoked to start this process. This is typically set by the shell when a .lnk file pointing to the launched application is invoked. Most applications will not need to set this value.
|
||||
/// This flag cannot be used with STARTF_TITLEISAPPID.
|
||||
/// </summary>
|
||||
STARTF_TITLEISLINKNAME = 0x00000800,
|
||||
|
||||
/// <summary>
|
||||
/// The command line came from an untrusted source.
|
||||
/// </summary>
|
||||
STARTF_UNTRUSTEDSOURCE = 0x00008000,
|
||||
|
||||
/// <summary>
|
||||
/// The dwXCountChars and dwYCountChars members contain additional information.
|
||||
/// </summary>
|
||||
STARTF_USECOUNTCHARS = 0x00000008,
|
||||
|
||||
/// <summary>
|
||||
/// The dwFillAttribute member contains additional information.
|
||||
/// </summary>
|
||||
STARTF_USEFILLATTRIBUTE = 0x00000010,
|
||||
|
||||
/// <summary>
|
||||
/// The hStdInput member contains additional information.
|
||||
/// This flag cannot be used with STARTF_USESTDHANDLES.
|
||||
/// </summary>
|
||||
STARTF_USEHOTKEY = 0x00000200,
|
||||
|
||||
/// <summary>
|
||||
/// The dwX and dwY members contain additional information.
|
||||
/// </summary>
|
||||
STARTF_USEPOSITION = 0x00000004,
|
||||
|
||||
/// <summary>
|
||||
/// The wShowWindow member contains additional information.
|
||||
/// </summary>
|
||||
STARTF_USESHOWWINDOW = 0x00000001,
|
||||
|
||||
/// <summary>
|
||||
/// The dwXSize and dwYSize members contain additional information.
|
||||
/// </summary>
|
||||
STARTF_USESIZE = 0x00000002,
|
||||
|
||||
/// <summary>
|
||||
/// The hStdInput, hStdOutput, and hStdError members contain additional information.
|
||||
/// If this flag is specified when calling one of the process creation functions, the handles must be inheritable and the function's bInheritHandles parameter must be set to TRUE. For more information, see Handle Inheritance.
|
||||
/// If this flag is specified when calling the GetStartupInfo function, these members are either the handle value specified during process creation or INVALID_HANDLE_VALUE.
|
||||
/// Handles must be closed with CloseHandle when they are no longer needed.
|
||||
/// This flag cannot be used with STARTF_USEHOTKEY.
|
||||
/// </summary>
|
||||
STARTF_USESTDHANDLES = 0x00000100
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal class StartupInfo
|
||||
{
|
||||
public int cb;
|
||||
public IntPtr lpReserved = IntPtr.Zero;
|
||||
public IntPtr lpDesktop = IntPtr.Zero;
|
||||
public IntPtr lpTitle = IntPtr.Zero;
|
||||
public int dwX;
|
||||
public int dwY;
|
||||
public int dwXSize;
|
||||
public int dwYSize;
|
||||
public int dwXCountChars;
|
||||
public int dwYCountChars;
|
||||
public int dwFillAttribute;
|
||||
public StartupFlags dwFlags;
|
||||
public short wShowWindow;
|
||||
public short cbReserved2;
|
||||
public IntPtr lpReserved2 = IntPtr.Zero;
|
||||
public IntPtr hStdInput = IntPtr.Zero;
|
||||
public IntPtr hStdOutput = IntPtr.Zero;
|
||||
public IntPtr hStdError = IntPtr.Zero;
|
||||
|
||||
public StartupInfo()
|
||||
{
|
||||
cb = Marshal.SizeOf(this);
|
||||
}
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal class SecurityAttributes
|
||||
{
|
||||
public int nLength = 0;
|
||||
|
||||
public IntPtr lpSecurityDescriptor = IntPtr.Zero;
|
||||
|
||||
public bool bInheritHandle = false;
|
||||
|
||||
public SecurityAttributes()
|
||||
{
|
||||
this.nLength = Marshal.SizeOf(this);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The different process creation flags that can be used.
|
||||
/// For more info see here: https://learn.microsoft.com/en-us/windows/win32/procthread/process-creation-flags
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum CreationFlags : int
|
||||
{
|
||||
/// <summary>
|
||||
/// No flags are set.
|
||||
/// </summary>
|
||||
NONE = 0,
|
||||
|
||||
/// <summary>
|
||||
/// The child processes of a process associated with a job are not associated with the job.
|
||||
/// If the calling process is not associated with a job, this constant has no effect. If the calling process is associated with a job, the job must set the JOB_OBJECT_LIMIT_BREAKAWAY_OK limit.
|
||||
/// </summary>
|
||||
CREATE_BREAKAWAY_FROM_JOB = 0x01000000,
|
||||
|
||||
/// <summary>
|
||||
/// The new process does not inherit the error mode of the calling process. Instead, the new process gets the default error mode.
|
||||
/// This feature is particularly useful for multithreaded shell applications that run with hard errors disabled.
|
||||
/// The default behavior is for the new process to inherit the error mode of the caller.Setting this flag changes that default behavior.
|
||||
/// </summary>
|
||||
CREATE_DEFAULT_ERROR_MODE = 0x04000000,
|
||||
|
||||
/// <summary>
|
||||
/// The new process has a new console, instead of inheriting its parent's console (the default). This flag cannot be used with DETACHED_PROCESS.
|
||||
/// </summary>
|
||||
CREATE_NEW_CONSOLE = 0x00000010,
|
||||
|
||||
/// <summary>
|
||||
/// The process is a console application that is being run without a console window. Therefore, the console handle for the application is not set.
|
||||
/// This flag is ignored if the application is not a console application, or if it is used with either CREATE_NEW_CONSOLE or DETACHED_PROCESS.
|
||||
/// </summary>
|
||||
CREATE_NO_WINDOW = 0x08000000,
|
||||
|
||||
/// <summary>
|
||||
/// The process is to be run as a protected process. The system restricts access to protected processes and the threads of protected processes.
|
||||
/// </summary>
|
||||
CREATE_PROTECTED_PROCESS = 0x00040000,
|
||||
|
||||
/// <summary>
|
||||
/// Allows the caller to execute a child process that bypasses the process restrictions that would normally be applied automatically to the process.
|
||||
/// </summary>
|
||||
CREATE_PRESERVE_CODE_AUTHZ_LEVEL = 0x02000000,
|
||||
|
||||
/// <summary>
|
||||
/// This flag allows secure processes, that run in the Virtualization-Based Security environment, to launch.
|
||||
/// </summary>
|
||||
CREATE_SECURE_PROCESS = 0x00400000,
|
||||
|
||||
/// <summary>
|
||||
/// This flag is valid only when starting a 16-bit Windows-based application. If set, the new process runs in a private Virtual DOS Machine (VDM). By default, all 16-bit Windows-based applications run as threads in a single, shared VDM.
|
||||
/// </summary>
|
||||
CREATE_SEPARATE_WOW_VDM = 0x00000800,
|
||||
|
||||
/// <summary>
|
||||
/// The flag is valid only when starting a 16-bit Windows-based application. If the DefaultSeparateVDM switch in the Windows section of WIN.INI is TRUE, this flag overrides the switch. The new process is run in the shared Virtual DOS Machine.
|
||||
/// </summary>
|
||||
CREATE_SHARED_WOW_VDM = 0x00001000,
|
||||
|
||||
/// <summary>
|
||||
/// The primary thread of the new process is created in a suspended state, and does not run until the ResumeThread function is called.
|
||||
/// </summary>
|
||||
CREATE_SUSPENDED = 0x00000004,
|
||||
|
||||
/// <summary>
|
||||
/// If this flag is set, the environment block pointed to by lpEnvironment uses Unicode characters. Otherwise, the environment block uses ANSI characters.
|
||||
/// </summary>
|
||||
CREATE_UNICODE_ENVIRONMENT = 0x00000400,
|
||||
|
||||
/// <summary>
|
||||
/// The calling thread starts and debugs the new process. It can receive all related debug events using the WaitForDebugEvent function.
|
||||
/// </summary>
|
||||
DEBUG_ONLY_THIS_PROCESS = 0x00000002,
|
||||
|
||||
/// <summary>
|
||||
/// The calling thread starts and debugs the new process and all child processes created by the new process. It can receive all related debug events using the WaitForDebugEvent function.
|
||||
/// A process that uses DEBUG_PROCESS becomes the root of a debugging chain. This continues until another process in the chain is created with DEBUG_PROCESS.
|
||||
/// If this flag is combined with DEBUG_ONLY_THIS_PROCESS, the caller debugs only the new process, not any child processes.
|
||||
/// </summary>
|
||||
DEBUG_PROCESS = 0x00000001,
|
||||
|
||||
/// <summary>
|
||||
/// For console processes, the new process does not inherit its parent's console (the default). The new process can call the AllocConsole function at a later time to create a console. For more information, see Creation of a Console.
|
||||
/// This value cannot be used with CREATE_NEW_CONSOLE.
|
||||
/// </summary>
|
||||
DETACHED_PROCESS = 0x00000008,
|
||||
|
||||
/// <summary>
|
||||
/// The process is created with extended startup information; the lpStartupInfo parameter specifies a STARTUPINFOEX structure.
|
||||
/// </summary>
|
||||
EXTENDED_STARTUPINFO_PRESENT = 0x00080000,
|
||||
|
||||
/// <summary>
|
||||
/// he process inherits its parent's affinity. If the parent process has threads in more than one processor group, the new process inherits the group-relative affinity of an arbitrary group in use by the parent.
|
||||
/// </summary>
|
||||
INHERIT_PARENT_AFFINITY = 0x00010000
|
||||
}
|
||||
|
||||
[DllImport("Kernel32", CharSet = CharSet.Auto, SetLastError = true, BestFitMapping = false)]
|
||||
internal static extern bool CreateProcess(
|
||||
[MarshalAs(UnmanagedType.LPTStr)] string applicationName,
|
||||
StringBuilder commandLine,
|
||||
SecurityAttributes processAttributes,
|
||||
SecurityAttributes threadAttributes,
|
||||
bool inheritHandles,
|
||||
CreationFlags creationFlags,
|
||||
IntPtr environment,
|
||||
[MarshalAs(UnmanagedType.LPTStr)] string currentDirectory,
|
||||
StartupInfo startupInfo,
|
||||
ProcessInformation processInformation
|
||||
);
|
||||
|
||||
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
|
||||
internal static extern bool CreatePipe(
|
||||
out SafeFileHandle hReadPipe,
|
||||
out SafeFileHandle hWritePipe,
|
||||
SecurityAttributes lpPipeAttributes,
|
||||
int nSize
|
||||
);
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
internal static extern bool DuplicateHandle(
|
||||
IntPtr hSourceProcessHandle,
|
||||
IntPtr hSourceHandle,
|
||||
IntPtr hTargetProcessHandle,
|
||||
out SafeFileHandle lpTargetHandle,
|
||||
uint dwDesiredAccess,
|
||||
[MarshalAs(UnmanagedType.Bool)] bool bInheritHandle,
|
||||
uint dwOptions
|
||||
);
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
internal static extern int GetConsoleOutputCP();
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
internal static extern int GetConsoleCP();
|
||||
|
||||
internal static void CreatePipeWithSecurityAttributes(out SafeFileHandle hReadPipe, out SafeFileHandle hWritePipe, ref SecurityAttributes lpPipeAttributes, int nSize)
|
||||
{
|
||||
bool ret = CreatePipe(out hReadPipe, out hWritePipe, lpPipeAttributes, nSize);
|
||||
|
||||
if (!ret || hReadPipe.IsInvalid || hWritePipe.IsInvalid)
|
||||
throw new Exception("Failed to create pipe");
|
||||
}
|
||||
|
||||
internal static void CreatePipe(out SafeFileHandle parentHandle, out SafeFileHandle childHandle, bool parentInputs)
|
||||
{
|
||||
SecurityAttributes securityAttributesParent = new SecurityAttributes();
|
||||
securityAttributesParent.bInheritHandle = true;
|
||||
|
||||
SafeFileHandle hTmp = null;
|
||||
try
|
||||
{
|
||||
if (parentInputs)
|
||||
CreatePipeWithSecurityAttributes(out childHandle, out hTmp, ref securityAttributesParent, 0);
|
||||
else
|
||||
CreatePipeWithSecurityAttributes(out hTmp, out childHandle, ref securityAttributesParent, 0);
|
||||
|
||||
var currentProcessHandle = System.Diagnostics.Process.GetCurrentProcess().Handle;
|
||||
|
||||
if (!DuplicateHandle(currentProcessHandle, hTmp.DangerousGetHandle(), currentProcessHandle, out parentHandle, 0, false, 0x00000002))
|
||||
throw new Exception("Failed to duplicate handle");
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (hTmp != null && !hTmp.IsInvalid)
|
||||
hTmp.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
internal static void BuildCommandLine(System.Diagnostics.ProcessStartInfo startInfo, ref StringBuilder commandLine)
|
||||
{
|
||||
var fileName = startInfo.FileName.AsSpan().Trim();
|
||||
|
||||
bool fileNameIsQuoted = fileName.Length > 0 && fileName[0] == '\"' && fileName[fileName.Length - 1] == '\"';
|
||||
|
||||
if (!fileNameIsQuoted)
|
||||
commandLine.Append('"');
|
||||
|
||||
commandLine.Append(fileName);
|
||||
|
||||
if (!fileNameIsQuoted)
|
||||
commandLine.Append('"');
|
||||
|
||||
if (startInfo.Arguments != null)
|
||||
commandLine.Append(" ").Append(startInfo.Arguments);
|
||||
}
|
||||
|
||||
internal static Encoding GetEncoding(int codePage)
|
||||
{
|
||||
//It appears that 437 is the same as Latin1
|
||||
if (codePage == 437)
|
||||
return Encoding.Latin1;
|
||||
|
||||
return Encoding.GetEncoding(codePage);
|
||||
}
|
||||
|
||||
internal static string GetEnvironmentVariablesBlock(StringDictionary variables)
|
||||
{
|
||||
//All environmental variables must be sorted alphabetically
|
||||
var keys = new string[variables.Count];
|
||||
variables.Keys.CopyTo(keys, 0);
|
||||
Array.Sort(keys, StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
//Join the null-terminated "key=val\0" strings
|
||||
var result = new StringBuilder(8 * keys.Length);
|
||||
|
||||
foreach (string key in keys)
|
||||
result.Append(key).Append('=').Append(variables[key]).Append('\0');
|
||||
|
||||
return result.ToString();
|
||||
}
|
||||
|
||||
internal static short GetShowWindowFromWindowStyle(ProcessWindowStyle windowStyle)
|
||||
{
|
||||
switch (windowStyle)
|
||||
{
|
||||
case ProcessWindowStyle.Hidden:
|
||||
return 0;
|
||||
|
||||
case ProcessWindowStyle.Maximized:
|
||||
return 3;
|
||||
|
||||
case ProcessWindowStyle.Minimized:
|
||||
return 6;
|
||||
|
||||
case ProcessWindowStyle.Normal:
|
||||
return 5;
|
||||
|
||||
default:
|
||||
return 5;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new C# Process using the Kernel32 CreateProcess function and returns the running process.
|
||||
/// </summary>
|
||||
/// <param name="startInfo">The startInfo needed to create the process, this cannot be null.</param>
|
||||
/// <param name="startupFlags">If supplied this overrides all the startup flags for the process. Default is null.</param>
|
||||
/// <param name="creationFlags">If supplied this overrides all the creation flags for the process. Default is null.</param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="Exception"></exception>
|
||||
public unsafe static System.Diagnostics.Process Create(System.Diagnostics.ProcessStartInfo startInfo, StartupFlags? startupFlags = null, CreationFlags? creationFlags = null)
|
||||
{
|
||||
if (startInfo == null)
|
||||
throw new ArgumentNullException(nameof(startInfo));
|
||||
|
||||
var process = new System.Diagnostics.Process();
|
||||
process.StartInfo = startInfo;
|
||||
|
||||
var processType = process.GetType();
|
||||
|
||||
var commandLine = new StringBuilder();
|
||||
|
||||
BuildCommandLine(startInfo, ref commandLine);
|
||||
|
||||
var processInfo = new ProcessInformation();
|
||||
|
||||
var startupInfo = new StartupInfo();
|
||||
|
||||
//Set the initial startup flags.
|
||||
startupInfo.dwFlags = startupFlags != null ? startupFlags.Value : StartupFlags.NONE;
|
||||
|
||||
//If we are changing the std handles, set the flag
|
||||
if (startInfo.RedirectStandardError || startInfo.RedirectStandardInput || startInfo.RedirectStandardOutput)
|
||||
startupInfo.dwFlags |= StartupFlags.STARTF_USESTDHANDLES;
|
||||
|
||||
var processSecurity = new SecurityAttributes();
|
||||
processSecurity.bInheritHandle = false;
|
||||
|
||||
var threadSecurity = new SecurityAttributes();
|
||||
threadSecurity.bInheritHandle = false;
|
||||
|
||||
SafeFileHandle parentInputHandle = null;
|
||||
SafeFileHandle childInputHandle = null;
|
||||
SafeFileHandle parentOutputHandle = null;
|
||||
SafeFileHandle childOutputHandle = null;
|
||||
SafeFileHandle parentErrorHandle = null;
|
||||
SafeFileHandle childErrorHandle = null;
|
||||
SafeProcessHandle processHandle = new SafeProcessHandle();
|
||||
|
||||
try
|
||||
{
|
||||
//Setup the standard input handle if needed
|
||||
if (startInfo.RedirectStandardInput)
|
||||
{
|
||||
CreatePipe(out parentInputHandle, out childInputHandle, true);
|
||||
|
||||
startupInfo.hStdInput = childInputHandle.DangerousGetHandle();
|
||||
}
|
||||
|
||||
//Setup the standard output handle if needed
|
||||
if (startInfo.RedirectStandardOutput)
|
||||
{
|
||||
CreatePipe(out parentOutputHandle, out childOutputHandle, false);
|
||||
|
||||
startupInfo.hStdOutput = childOutputHandle.DangerousGetHandle();
|
||||
}
|
||||
|
||||
//Setup the standard error handle if needed
|
||||
if (startInfo.RedirectStandardError)
|
||||
{
|
||||
CreatePipe(out parentErrorHandle, out childErrorHandle, false);
|
||||
|
||||
startupInfo.hStdError = childErrorHandle.DangerousGetHandle();
|
||||
}
|
||||
|
||||
//Setup the creation flags if needed.
|
||||
if (creationFlags == null)
|
||||
creationFlags = CreationFlags.NONE;
|
||||
|
||||
//Set create no window
|
||||
if (startInfo.CreateNoWindow)
|
||||
creationFlags |= CreationFlags.CREATE_NO_WINDOW;
|
||||
|
||||
//Setup the environmental variables
|
||||
string environmentBlock = null;
|
||||
if (startInfo.EnvironmentVariables != null && startInfo.EnvironmentVariables.Count > 0)
|
||||
{
|
||||
creationFlags |= CreationFlags.CREATE_UNICODE_ENVIRONMENT;
|
||||
|
||||
environmentBlock = GetEnvironmentVariablesBlock(startInfo.EnvironmentVariables);
|
||||
}
|
||||
|
||||
//Setup the window style
|
||||
if (startInfo.WindowStyle != ProcessWindowStyle.Normal)
|
||||
{
|
||||
startupInfo.wShowWindow = GetShowWindowFromWindowStyle(startInfo.WindowStyle);
|
||||
startupInfo.dwFlags |= StartupFlags.STARTF_USESHOWWINDOW;
|
||||
}
|
||||
|
||||
//Setup the working directory
|
||||
string workingDirectory = startInfo.WorkingDirectory;
|
||||
|
||||
if (workingDirectory?.Length == 0)
|
||||
workingDirectory = null;
|
||||
|
||||
//Create the process
|
||||
fixed (char* environmentBlockPtr = environmentBlock)
|
||||
if (!CreateProcess(null, commandLine, processSecurity, threadSecurity, true, creationFlags.Value, (IntPtr)environmentBlockPtr, workingDirectory, startupInfo, processInfo))
|
||||
throw new Exception("Failed to start process");
|
||||
|
||||
//Setup the process handle if we got it.
|
||||
if (processInfo.hProcess != IntPtr.Zero && processInfo.hProcess != new IntPtr(-1))
|
||||
Marshal.InitHandle(processHandle, processInfo.hProcess);
|
||||
}
|
||||
catch
|
||||
{
|
||||
parentInputHandle?.Dispose();
|
||||
parentOutputHandle?.Dispose();
|
||||
parentErrorHandle?.Dispose();
|
||||
processHandle.Dispose();
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
{
|
||||
childInputHandle?.Dispose();
|
||||
childOutputHandle?.Dispose();
|
||||
childErrorHandle?.Dispose();
|
||||
}
|
||||
|
||||
//Unsafely setup the process info.
|
||||
processType.GetField("_processId", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance).SetValue(process, (int)processInfo.dwProcessId);
|
||||
processType.GetField("_haveProcessId", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance).SetValue(process, true);
|
||||
processType.GetField("_processHandle", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance).SetValue(process, processHandle);
|
||||
|
||||
//Set the standard input writer if needed
|
||||
if (startInfo.RedirectStandardInput)
|
||||
{
|
||||
var writer = new StreamWriter(new FileStream(parentInputHandle, FileAccess.Write, 4096, false), startInfo.StandardInputEncoding ?? GetEncoding(GetConsoleCP()), 4096);
|
||||
writer.AutoFlush = true;
|
||||
|
||||
processType.GetField("_standardInput", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance).SetValue(process, writer);
|
||||
}
|
||||
|
||||
//Set the standard output reader if needed.
|
||||
if (startInfo.RedirectStandardOutput)
|
||||
{
|
||||
var reader = new StreamReader(new FileStream(parentOutputHandle, FileAccess.Read, 4096, false), startInfo.StandardOutputEncoding ?? GetEncoding(GetConsoleOutputCP()), true, 4096);
|
||||
|
||||
processType.GetField("_standardOutput", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance).SetValue(process, reader);
|
||||
}
|
||||
|
||||
//Set the standard error reader if needed.
|
||||
if (startInfo.RedirectStandardError)
|
||||
{
|
||||
var reader = new StreamReader(new FileStream(parentErrorHandle, FileAccess.Read, 4096, false), startInfo.StandardErrorEncoding ?? GetEncoding(GetConsoleOutputCP()), true, 4096);
|
||||
|
||||
processType.GetField("_standardError", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance).SetValue(process, reader);
|
||||
}
|
||||
|
||||
return process;
|
||||
}
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user