├── src ├── Version │ ├── .gitignore │ └── GlobalAssemblyInfo.cs ├── AppSecInc.ProcessDomain.UnitTests │ ├── OtherApp.config │ ├── packages.config │ ├── App.config │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── RemoteTestObject.cs │ ├── AppSecInc.ProcessDomain.UnitTests.csproj │ └── TestProcessDomain.cs ├── AppSecInc.ProcessDomain │ ├── Properties │ │ ├── AssemblyInfo.cs │ │ ├── Resources.Designer.cs │ │ └── Resources.resx │ ├── Remoting │ │ ├── ProcessStatus.cs │ │ ├── Attributes │ │ │ ├── PropertyNameAttribute.cs │ │ │ └── DefaultValueAttribute.cs │ │ ├── DeleteOnUnloadException.cs │ │ ├── IActivation.cs │ │ ├── AssemblyGeneratorCompilerException.cs │ │ ├── Activator.cs │ │ ├── ActivatorClient.cs │ │ ├── ActivatorHostAssemblyGenerator.cs │ │ ├── ActivatorHost.cs │ │ ├── RemotingOptions.cs │ │ └── ActivatorProcess.cs │ ├── Utils │ │ ├── AssemblyUtils.cs │ │ └── LoggingConfigurator.cs │ ├── WinApi.cs │ ├── PlatformTarget.cs │ ├── ProcessDomain.cs │ ├── AppSecInc.ProcessDomain.csproj │ ├── Resources │ │ └── Program.cs │ └── ProcessDomainSetup.cs ├── AppSecInc.ProcessDomain.Sample │ ├── RemoteException.cs │ ├── App.config │ ├── RemoteObject.cs │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── AppSecInc.ProcessDomain.Sample.csproj │ └── Program.cs └── AppSecInc.ProcessDomain.Host │ ├── Properties │ └── AssemblyInfo.cs │ └── AppSecInc.ProcessDomain.Host.csproj ├── .nuget ├── NuGet.exe ├── packages.config ├── NuGet.Config └── NuGet.targets ├── Version.proj ├── .github └── workflows │ ├── pull-request.yml │ └── release.yml ├── ProcessDomain.nuspec ├── README.md ├── ProcessDomain.proj ├── .gitignore ├── ProcessDomain.sln └── LICENSE /src/Version/.gitignore: -------------------------------------------------------------------------------- 1 | !.gitignore 2 | -------------------------------------------------------------------------------- /.nuget/NuGet.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/legendary-code/process-domain/HEAD/.nuget/NuGet.exe -------------------------------------------------------------------------------- /.nuget/packages.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /.nuget/NuGet.Config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/AppSecInc.ProcessDomain.UnitTests/OtherApp.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/AppSecInc.ProcessDomain/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.InteropServices; 3 | 4 | [assembly: AssemblyTitle("ProcessDomain")] 5 | [assembly: Guid("ED103427-51E1-428c-AA90-6F89BB81940D")] -------------------------------------------------------------------------------- /src/AppSecInc.ProcessDomain/Remoting/ProcessStatus.cs: -------------------------------------------------------------------------------- 1 | namespace AppSecInc.ProcessDomain.Remoting 2 | { 3 | internal enum ProcessStatus 4 | { 5 | Active, 6 | Killed, 7 | Terminated 8 | } 9 | } -------------------------------------------------------------------------------- /src/AppSecInc.ProcessDomain.UnitTests/packages.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/AppSecInc.ProcessDomain/Utils/AssemblyUtils.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace AppSecInc.ProcessDomain.Utils 4 | { 5 | public static class AssemblyUtils 6 | { 7 | public static string GetFilePathFromFileUri(string uri) 8 | { 9 | var fileUri = new Uri(uri); 10 | return Uri.UnescapeDataString(fileUri.AbsolutePath).Replace('/', '\\'); 11 | } 12 | } 13 | } -------------------------------------------------------------------------------- /src/AppSecInc.ProcessDomain.UnitTests/App.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/AppSecInc.ProcessDomain.Sample/RemoteException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.Serialization; 3 | 4 | namespace AppSecInc.ProcessDomain.Sample 5 | { 6 | [Serializable] 7 | public class RemoteException : Exception 8 | { 9 | public RemoteException(SerializationInfo info, StreamingContext context) 10 | : base(info, context) 11 | { 12 | } 13 | 14 | public RemoteException(string message) 15 | : base(message) 16 | { 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/AppSecInc.ProcessDomain/Remoting/Attributes/PropertyNameAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace AppSecInc.ProcessDomain.Remoting.Attributes 6 | { 7 | [AttributeUsage(AttributeTargets.Property)] 8 | public sealed class PropertyNameAttribute : Attribute 9 | { 10 | public PropertyNameAttribute(string name) 11 | { 12 | Name = name; 13 | } 14 | 15 | public PropertyNameAttribute() 16 | { 17 | } 18 | 19 | public string Name { get; set; } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/AppSecInc.ProcessDomain/Remoting/Attributes/DefaultValueAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace AppSecInc.ProcessDomain.Remoting.Attributes 6 | { 7 | [AttributeUsage(AttributeTargets.Property)] 8 | public sealed class DefaultValueAttribute : Attribute 9 | { 10 | public DefaultValueAttribute(object value) 11 | { 12 | Value = value; 13 | } 14 | 15 | public DefaultValueAttribute() 16 | { 17 | } 18 | 19 | public object Value { get; set; } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/AppSecInc.ProcessDomain/Remoting/DeleteOnUnloadException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.Serialization; 3 | 4 | namespace AppSecInc.ProcessDomain.Remoting 5 | { 6 | [Serializable] 7 | public class DeleteOnUnloadException : Exception 8 | { 9 | public DeleteOnUnloadException(SerializationInfo info, StreamingContext context) 10 | : base(info, context) 11 | { 12 | } 13 | 14 | public DeleteOnUnloadException(string message, Exception innerException) 15 | : base(message, innerException) 16 | { 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Version.proj: -------------------------------------------------------------------------------- 1 | 2 | 3 | Process Domain 4 | 5 | 6 | 7 | 8 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/Version/GlobalAssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.42000 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | [assembly: System.Runtime.InteropServices.ComVisible(false)] 12 | [assembly: System.Reflection.AssemblyProduct("Process Domain")] 13 | [assembly: System.Reflection.AssemblyFileVersion("1.10.0.0")] 14 | [assembly: System.Reflection.AssemblyVersion("1.10.0.0")] 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/AppSecInc.ProcessDomain/WinApi.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.InteropServices; 2 | using System.Text; 3 | 4 | namespace AppSecInc.ProcessDomain 5 | { 6 | internal static class WinApi 7 | { 8 | [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] 9 | [return: MarshalAs(UnmanagedType.Bool)] 10 | public static extern bool SetDllDirectory(string lpPathName); 11 | 12 | [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] 13 | public static extern int GetDllDirectory(int bufferLength, StringBuilder directory); 14 | 15 | [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] 16 | public static extern void SetLastError(uint dwErrCode); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /.github/workflows/pull-request.yml: -------------------------------------------------------------------------------- 1 | name: Pull Request 2 | 3 | on: pull_request 4 | 5 | jobs: 6 | build: 7 | runs-on: windows-latest 8 | steps: 9 | - name: Checkout 10 | uses: actions/checkout@v1 11 | 12 | - name: Setup MSBuild 13 | uses: microsoft/setup-msbuild@v1.0.2 14 | 15 | - name: Setup NuGet 16 | uses: nuget/setup-nuget@v1 17 | 18 | - name: Setup VSTest 19 | uses: darenm/Setup-VSTest@v1 20 | 21 | - name: Install NuGet Packages 22 | run: nuget restore ProcessDomain.sln 23 | 24 | - name: Build 25 | run: msbuild ProcessDomain.sln 26 | 27 | - name: Run Tests 28 | working-directory: src\AppSecInc.ProcessDomain.UnitTests\bin\Debug 29 | run: vstest.console.exe AppSecInc.ProcessDomain.UnitTests.dll 30 | -------------------------------------------------------------------------------- /src/AppSecInc.ProcessDomain/Remoting/IActivation.cs: -------------------------------------------------------------------------------- 1 | using System.Globalization; 2 | using System.Reflection; 3 | using System.Security.Policy; 4 | 5 | namespace AppSecInc.ProcessDomain.Remoting 6 | { 7 | /// 8 | /// Interface that the activator and process domain must implement to expose methods for creating object instances in a remote process 9 | /// 10 | public interface IActivation 11 | { 12 | object CreateInstanceAndUnwrap(string assemblyName, string typeName); 13 | object CreateInstanceAndUnwrap(string assemblyName, string typeName, object[] activationAttributes); 14 | object CreateInstanceAndUnwrap(string assemblyName, string typeName, bool ignoreCase, BindingFlags bindingAttr, Binder binder, object[] args, CultureInfo culture, object[] activationAttributes, Evidence securityAttributes); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/AppSecInc.ProcessDomain/PlatformTarget.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace AppSecInc.ProcessDomain 4 | { 5 | public enum PlatformTarget 6 | { 7 | AnyCPU, 8 | x86, 9 | Itanium, 10 | x64 11 | } 12 | 13 | public static class PlatformTargetUtil 14 | { 15 | public static string GetCompilerArgument(PlatformTarget target) 16 | { 17 | switch (target) 18 | { 19 | case PlatformTarget.AnyCPU: 20 | return "/platform:anycpu"; 21 | case PlatformTarget.Itanium: 22 | return "/platform:Itanium"; 23 | case PlatformTarget.x64: 24 | return "/platform:x64"; 25 | case PlatformTarget.x86: 26 | return "/platform:x86"; 27 | } 28 | 29 | throw new NotSupportedException("Unknown platform target specified"); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/AppSecInc.ProcessDomain.Sample/App.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/AppSecInc.ProcessDomain/Utils/LoggingConfigurator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using log4net.Config; 4 | 5 | namespace AppSecInc.ProcessDomain.Utils 6 | { 7 | /// 8 | /// Utility class for configuring log4net remotely in another process domain 9 | /// 10 | [Serializable] 11 | public class LoggingConfigurator : MarshalByRefObject 12 | { 13 | public static LoggingConfigurator CreateConfigurator(ProcessDomain domain) 14 | { 15 | return (LoggingConfigurator)domain.CreateInstanceAndUnwrap(typeof(LoggingConfigurator).Assembly.FullName, typeof(LoggingConfigurator).FullName); 16 | } 17 | 18 | public void ConfigureBasic() 19 | { 20 | BasicConfigurator.Configure(); 21 | } 22 | 23 | public void ConfigureAndWatchAppConfig() 24 | { 25 | XmlConfigurator.ConfigureAndWatch(new FileInfo(AppDomain.CurrentDomain.SetupInformation.ConfigurationFile)); 26 | } 27 | 28 | public void ConfigureAppConfig() 29 | { 30 | XmlConfigurator.Configure(new FileInfo(AppDomain.CurrentDomain.SetupInformation.ConfigurationFile)); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /ProcessDomain.nuspec: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ProcessDomain 5 | $version$ 6 | eternal0,vivekrathod 7 | eternal0,vivekrathod 8 | false 9 | MIT 10 | https://github.com/legendary-code/process-domain 11 | ProcessDomain implements a remoting solution for creating out-of-process AppDomains. It's written in C# and the assemblies will work with .NET Framework 2.0. 12 | Moving to GitHub actions for pull request checks and release process. Removing copyrights as the company no longer exists. 13 | AppDomain ProcessDomain Sandbox 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /src/AppSecInc.ProcessDomain/Remoting/AssemblyGeneratorCompilerException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.CodeDom.Compiler; 3 | using System.Runtime.Serialization; 4 | using System.Text; 5 | 6 | namespace AppSecInc.ProcessDomain.Remoting 7 | { 8 | [Serializable] 9 | public class AssemblyGeneratorCompilerException : Exception 10 | { 11 | public AssemblyGeneratorCompilerException(SerializationInfo info, StreamingContext context) 12 | : base(info, context) 13 | { 14 | } 15 | 16 | public AssemblyGeneratorCompilerException(string message, CompilerErrorCollection errors) 17 | : base(message) 18 | { 19 | Errors = errors; 20 | } 21 | 22 | public CompilerErrorCollection Errors 23 | { 24 | get; 25 | private set; 26 | } 27 | 28 | public override string ToString() 29 | { 30 | var sb = new StringBuilder(); 31 | sb.AppendLine(Message); 32 | 33 | foreach (CompilerError error in Errors) 34 | { 35 | if (error.IsWarning) 36 | continue; 37 | sb.AppendFormat("{0}\r\n", error); 38 | } 39 | 40 | return sb.ToString(); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | release: 5 | types: 6 | - published 7 | 8 | jobs: 9 | release: 10 | runs-on: windows-latest 11 | steps: 12 | - name: Get Release Version 13 | id: get_version 14 | run: echo ::set-output name=VERSION::${GITHUB_REF/refs\/tags\//} 15 | shell: bash 16 | 17 | - uses: actions/checkout@v1 18 | name: Checkout 19 | 20 | - name: Setup MSBuild 21 | uses: microsoft/setup-msbuild@v1.0.2 22 | 23 | - name: Setup NuGet 24 | uses: nuget/setup-nuget@v1 25 | with: 26 | nuget-api-key: ${{ secrets.NUGET_API_KEY }} 27 | 28 | - name: Install NuGet Packages 29 | run: nuget restore ProcessDomain.sln 30 | 31 | - name: Install MSBuildTasks Package 32 | run: nuget install MSBuildTasks -Version 1.5.0.235 33 | 34 | - name: Generate GlobalAssemblyInfo.cs 35 | run: msbuild .\Version.proj /p:Version=${{ steps.get_version.outputs.VERSION }} 36 | 37 | - name: Build Release 38 | run: msbuild ProcessDomain.sln /property:Configuration=Release 39 | 40 | - name: Create NuGet Package 41 | run: nuget pack .\ProcessDomain.nuspec -p version=${{ steps.get_version.outputs.VERSION }} 42 | 43 | - name: Publish NuGet Package 44 | run: nuget push ProcessDomain.${{ steps.get_version.outputs.VERSION }}.nupkg -Source https://api.nuget.org/v3/index.json 45 | -------------------------------------------------------------------------------- /src/AppSecInc.ProcessDomain.Sample/RemoteObject.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Configuration; 3 | using System.Diagnostics; 4 | using System.IO; 5 | using log4net; 6 | using log4net.Config; 7 | 8 | namespace AppSecInc.ProcessDomain.Sample 9 | { 10 | [Serializable] 11 | public class RemoteObject : MarshalByRefObject 12 | { 13 | static readonly ILog Logger = LogManager.GetLogger(typeof(RemoteObject)); 14 | 15 | public void ConfigLogging() 16 | { 17 | XmlConfigurator.ConfigureAndWatch(new FileInfo(AppDomain.CurrentDomain.SetupInformation.ConfigurationFile)); 18 | } 19 | 20 | public string GetProcessMainModuleFileName() 21 | { 22 | Logger.Info("Called GetProcessMainModuleFileName()"); 23 | return Process.GetCurrentProcess().MainModule.FileName; 24 | } 25 | 26 | public string GetAppConfigLocation() 27 | { 28 | Logger.Info("Called GetAppConfigLocation()"); 29 | return AppDomain.CurrentDomain.SetupInformation.ConfigurationFile; 30 | } 31 | 32 | public string GetAppConfigValue(string key) 33 | { 34 | Logger.Info("Called GetAppConfigValue()"); 35 | return ConfigurationManager.AppSettings[key]; 36 | } 37 | 38 | public void ThrowException() 39 | { 40 | throw new RemoteException("This is an exception"); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/AppSecInc.ProcessDomain.Host/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.InteropServices; 3 | 4 | // General Information about an assembly is controlled through the following 5 | // set of attributes. Change these attribute values to modify the information 6 | // associated with an assembly. 7 | [assembly: AssemblyConfiguration("")] 8 | [assembly: AssemblyCompany("Microsoft")] 9 | [assembly: AssemblyProduct("AppSecInc.ProcessDomain.Host")] 10 | [assembly: AssemblyCopyright("Copyright © Microsoft 2010")] 11 | [assembly: AssemblyTrademark("")] 12 | [assembly: AssemblyCulture("")] 13 | 14 | // Setting ComVisible to false makes the types in this assembly not visible 15 | // to COM components. If you need to access a type in this assembly from 16 | // COM, set the ComVisible attribute to true on that type. 17 | [assembly: ComVisible(false)] 18 | 19 | // The following GUID is for the ID of the typelib if this project is exposed to COM 20 | [assembly: Guid("76a40f2f-56dd-4171-8a69-4ca649b59799")] 21 | 22 | // Version information for an assembly consists of the following four values: 23 | // 24 | // Major Version 25 | // Minor Version 26 | // Build Number 27 | // Revision 28 | // 29 | // You can specify all the values or you can default the Build and Revision Numbers 30 | // by using the '*' as shown below: 31 | // [assembly: AssemblyVersion("1.0.*")] 32 | [assembly: AssemblyVersion("1.0.0.0")] 33 | [assembly: AssemblyFileVersion("1.0.0.0")] 34 | -------------------------------------------------------------------------------- /src/AppSecInc.ProcessDomain.UnitTests/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("TestProcessDomain")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("Microsoft")] 12 | [assembly: AssemblyProduct("TestProcessDomain")] 13 | [assembly: AssemblyCopyright("Copyright © Microsoft 2010")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("0031e17e-2cfc-4b5f-9f46-ddd220cfd9aa")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /src/AppSecInc.ProcessDomain.Sample/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("AppSecInc.ProcessDomain.Sample")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("Microsoft")] 12 | [assembly: AssemblyProduct("AppSecInc.ProcessDomain.Sample")] 13 | [assembly: AssemblyCopyright("Copyright © Microsoft 2010")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("4213161e-80b0-4913-a9e3-850298170f18")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ProcessDomain 2 | 3 | ProcessDomain implements a remoting solution for creating out-of-process AppDomains. It's written in C# and the assemblies will work with .NET Framework 2.0. Some possible usages include: 4 | * Further code isolation 5 | * Allow for native code to run with different environment variables 6 | * Allow for multiple versions of a native library to be loaded without writing separate applications. E.g. A managed application that uses native database drivers, but needs to support loading multiple versions so that connectivity can occur for multiple versions of the database. 7 | 8 | See the example project for some usages 9 | 10 | # Features 11 | 12 | * AppDomain-like semantics, so its usage is familiar and easy 13 | * Implements the IDisposable pattern 14 | * Event handlers for when the remote process exits or is restarted 15 | * Automatic restart of remote process supported 16 | * Remote process assembly is generated on-the-fly at runtime 17 | * Remote process' AppDomain is fully configurable and by default takes on the settings of the AppDomain creating the ProcessDomain 18 | 19 | # Installation 20 | 21 | As of version 1.8, ProcessDomain will be available via NuGet as well: https://nuget.org/packages/ProcessDomain/1.8 22 | 23 | # Requirements 24 | 25 | ### To Build Source 26 | 27 | Visual Studio 2013 28 | [MSBuild Community Tasks 1.3](http://msbuildtasks.tigris.org/MSBuild.Community.Tasks.Nightly.msi) 29 | 30 | ### To Use Assemblies 31 | .NET Framework 2.0 32 | 33 | ### T Publish a Release to NuGet 34 | 35 | Simply publish a GitHub Release with the next correct semantic version, and the GitHub Actions workflow will take of building the package and publishing it to NuGet. 36 | -------------------------------------------------------------------------------- /src/AppSecInc.ProcessDomain/Remoting/Activator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using System.Reflection; 4 | using System.Security.Policy; 5 | 6 | namespace AppSecInc.ProcessDomain.Remoting 7 | { 8 | /// 9 | /// This is a remotable object responsible for creating instances of objects remotely in another process 10 | /// 11 | [Serializable] 12 | internal class Activator : MarshalByRefObject, IActivation 13 | { 14 | // Make sure that the object instance is never garbage collected because 15 | // it's not gc-rooted 16 | public override object InitializeLifetimeService() 17 | { 18 | return null; 19 | } 20 | 21 | public object CreateInstanceAndUnwrap(string assemblyName, string typeName) 22 | { 23 | return AppDomain.CurrentDomain.CreateInstanceAndUnwrap(assemblyName, typeName); 24 | } 25 | 26 | public object CreateInstanceAndUnwrap(string assemblyName, string typeName, object[] activationAttributes) 27 | { 28 | return AppDomain.CurrentDomain.CreateInstanceAndUnwrap(assemblyName, typeName, activationAttributes); 29 | } 30 | 31 | public object CreateInstanceAndUnwrap(string assemblyName, string typeName, bool ignoreCase, BindingFlags bindingAttr, Binder binder, object[] args, CultureInfo culture, object[] activationAttributes, Evidence securityAttributes) 32 | { 33 | return AppDomain.CurrentDomain.CreateInstanceAndUnwrap(assemblyName, typeName, ignoreCase, bindingAttr, binder, args, culture, activationAttributes, securityAttributes); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/AppSecInc.ProcessDomain/Remoting/ActivatorClient.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Runtime.Remoting.Channels; 4 | using System.Runtime.Remoting.Channels.Ipc; 5 | 6 | namespace AppSecInc.ProcessDomain.Remoting 7 | { 8 | /// 9 | /// This class provides access to an Activator in a remote process 10 | /// 11 | internal class ActivatorClient : IDisposable 12 | { 13 | readonly Activator _activator; 14 | readonly IChannel _channel; 15 | 16 | public ActivatorClient(string guid, ProcessDomainSetup setup) 17 | { 18 | var serverProvider = new BinaryServerFormatterSinkProvider { TypeFilterLevel = setup.TypeFilterLevel }; 19 | var clientProvider = new BinaryClientFormatterSinkProvider(); 20 | 21 | var properties = new Hashtable(); 22 | properties["portName"] = string.Format(ActivatorHost.ClientChannelName, guid); 23 | properties["name"] = string.Format(ActivatorHost.ClientChannelName, guid); 24 | setup.Remoting.ApplyClientProperties(properties); 25 | 26 | _channel = new IpcChannel(properties, clientProvider, serverProvider); 27 | ChannelServices.RegisterChannel(_channel, false); 28 | 29 | _activator = (Activator)System.Activator.GetObject(typeof(Activator), string.Format("ipc://{0}/{1}", string.Format(ActivatorHost.ServerChannelName, guid), ActivatorHost.ActivatorName)); 30 | } 31 | 32 | public Activator Activator 33 | { 34 | get { return _activator; } 35 | } 36 | 37 | public void Dispose() 38 | { 39 | ChannelServices.UnregisterChannel(_channel); 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/AppSecInc.ProcessDomain.UnitTests/RemoteTestObject.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Configuration; 3 | using System.Diagnostics; 4 | using System.Security.Principal; 5 | using System.Threading; 6 | 7 | namespace AppSecInc.ProcessDomain.UnitTests 8 | { 9 | [Serializable] 10 | public class RemoteTestObject : MarshalByRefObject 11 | { 12 | public delegate void Callback(); 13 | public event Callback CallbackEvent; 14 | 15 | public override object InitializeLifetimeService() 16 | { 17 | return null; 18 | } 19 | 20 | public string GetProcessFileName() 21 | { 22 | return Process.GetCurrentProcess().MainModule.FileName; 23 | } 24 | 25 | public int GetProcessId() 26 | { 27 | return Process.GetCurrentProcess().Id; 28 | } 29 | 30 | public string CurrentDirectory 31 | { 32 | get { return Environment.CurrentDirectory; } 33 | } 34 | 35 | public string GetAppConfigValue(string key) 36 | { 37 | return ConfigurationManager.AppSettings[key]; 38 | } 39 | 40 | public ProcessPriorityClass GetPriority() 41 | { 42 | return Process.GetCurrentProcess().PriorityClass; 43 | } 44 | 45 | public bool RunningAsAdministrator() 46 | { 47 | var identity = WindowsIdentity.GetCurrent(); 48 | var principal = new WindowsPrincipal(identity); 49 | return principal.IsInRole(WindowsBuiltInRole.Administrator); 50 | } 51 | 52 | public void OnCallback() 53 | { 54 | if (CallbackEvent != null) 55 | { 56 | CallbackEvent(); 57 | } 58 | } 59 | 60 | public bool CalledBack { get; set; } 61 | 62 | /// 63 | /// This method is used for CallbackEvent because the target method needs to be in a class 64 | /// that is also serializable for subscribing to events to work 65 | /// 66 | public void SetCalledBack() 67 | { 68 | CalledBack = true; 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/AppSecInc.ProcessDomain.Host/AppSecInc.ProcessDomain.Host.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Debug 5 | AnyCPU 6 | 9.0.21022 7 | 2.0 8 | {81E2FA83-26A1-4BE3-8F38-2C7327EC7840} 9 | WinExe 10 | Properties 11 | AppSecInc.ProcessDomain.Host 12 | AppSecInc.ProcessDomain.Host 13 | v2.0 14 | 512 15 | 16 | 17 | 18 | 19 | 20 | 21 | 3.5 22 | 23 | 24 | true 25 | full 26 | false 27 | bin\Debug\ 28 | DEBUG;TRACE 29 | prompt 30 | 4 31 | 32 | 33 | pdbonly 34 | true 35 | bin\Release\ 36 | TRACE 37 | prompt 38 | 4 39 | 40 | 41 | 42 | 43 | 44 | 45 | Program.cs 46 | 47 | 48 | 49 | 50 | 57 | -------------------------------------------------------------------------------- /ProcessDomain.proj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug;Release 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /src/AppSecInc.ProcessDomain/Properties/Resources.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.34209 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace AppSecInc.ProcessDomain.Properties { 12 | using System; 13 | 14 | 15 | /// 16 | /// A strongly-typed resource class, for looking up localized strings, etc. 17 | /// 18 | // This class was auto-generated by the StronglyTypedResourceBuilder 19 | // class via a tool like ResGen or Visual Studio. 20 | // To add or remove a member, edit your .ResX file then rerun ResGen 21 | // with the /str option, or rebuild your VS project. 22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] 23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 25 | internal class Resources { 26 | 27 | private static global::System.Resources.ResourceManager resourceMan; 28 | 29 | private static global::System.Globalization.CultureInfo resourceCulture; 30 | 31 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] 32 | internal Resources() { 33 | } 34 | 35 | /// 36 | /// Returns the cached ResourceManager instance used by this class. 37 | /// 38 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 39 | internal static global::System.Resources.ResourceManager ResourceManager { 40 | get { 41 | if (object.ReferenceEquals(resourceMan, null)) { 42 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("AppSecInc.ProcessDomain.Properties.Resources", typeof(Resources).Assembly); 43 | resourceMan = temp; 44 | } 45 | return resourceMan; 46 | } 47 | } 48 | 49 | /// 50 | /// Overrides the current thread's CurrentUICulture property for all 51 | /// resource lookups using this strongly typed resource class. 52 | /// 53 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 54 | internal static global::System.Globalization.CultureInfo Culture { 55 | get { 56 | return resourceCulture; 57 | } 58 | set { 59 | resourceCulture = value; 60 | } 61 | } 62 | 63 | /// 64 | /// Looks up a localized string 65 | /// 66 | internal static string Program { 67 | get { 68 | return ResourceManager.GetString("Program", resourceCulture); 69 | } 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/AppSecInc.ProcessDomain/Remoting/ActivatorHostAssemblyGenerator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.CodeDom.Compiler; 3 | using System.Collections.Generic; 4 | using System.IO; 5 | using System.Reflection; 6 | 7 | using log4net; 8 | using Microsoft.CSharp; 9 | 10 | namespace AppSecInc.ProcessDomain.Remoting 11 | { 12 | /// 13 | /// Generates an assembly to run in a separate process in order to host an Activator 14 | /// 15 | internal static class ActivatorHostAssemblyGenerator 16 | { 17 | static readonly ILog Logger = LogManager.GetLogger(typeof(ActivatorHostAssemblyGenerator)); 18 | const string AssemblyName = @"{0}.exe"; 19 | private static readonly String[] ReferencedAssemblies = new[] { "System.dll" }; 20 | 21 | public static string CreateRemoteHostAssembly(string friendlyName, ProcessDomainSetup setupInfo) 22 | { 23 | if (!Directory.Exists(setupInfo.ExecutableDirectory)) 24 | { 25 | Directory.CreateDirectory(setupInfo.ExecutableDirectory); 26 | } 27 | 28 | var providerOptions = new Dictionary 29 | { 30 | { "CompilerVersion", setupInfo.CompilerVersion ?? ProcessDomainSetup.DefaultCompilerVersion } 31 | }; 32 | 33 | var provider = new CSharpCodeProvider(providerOptions); 34 | 35 | var compilerArgs = new List {PlatformTargetUtil.GetCompilerArgument(setupInfo.Platform)}; 36 | 37 | var compilerParameters = new CompilerParameters 38 | { 39 | GenerateExecutable = true, 40 | GenerateInMemory = false, 41 | CompilerOptions = string.Join(" ", compilerArgs.ToArray()), 42 | OutputAssembly = Path.Combine(setupInfo.ExecutableDirectory, 43 | string.Format(AssemblyName, friendlyName)) 44 | }; 45 | 46 | compilerParameters.ReferencedAssemblies.AddRange(ReferencedAssemblies); 47 | 48 | string assemblySource = Properties.Resources.Program 49 | .Replace("${ActivatorHostTypeName}", typeof (ActivatorHost).AssemblyQualifiedName) 50 | .Replace("${ProcessDomainAssemblyName}", typeof(ActivatorHost).Assembly.FullName); 51 | 52 | var results = provider.CompileAssemblyFromSource(compilerParameters, assemblySource); 53 | 54 | if (results.Errors.HasErrors) 55 | { 56 | throw new AssemblyGeneratorCompilerException("Failed to compile assembly for process domain due to compiler errors", results.Errors); 57 | } 58 | 59 | if (results.Errors.HasWarnings) 60 | { 61 | Logger.Warn("Process domain assembly compilation returned with warnings:"); 62 | foreach (var error in results.Errors) 63 | { 64 | Logger.WarnFormat("Compiler Warning: {0}", error); 65 | } 66 | } 67 | if (setupInfo.CopyConfigurationFile) 68 | { 69 | File.Copy(setupInfo.AppDomainSetupInformation.ConfigurationFile, 70 | results.PathToAssembly + ".config", true); 71 | } 72 | return results.PathToAssembly; 73 | } 74 | } 75 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | build/ 21 | bld/ 22 | [Bb]in/ 23 | [Oo]bj/ 24 | 25 | # Visual Studo 2015 cache/options directory 26 | .vs/ 27 | 28 | # MSTest test Results 29 | [Tt]est[Rr]esult*/ 30 | [Bb]uild[Ll]og.* 31 | 32 | # NUNIT 33 | *.VisualState.xml 34 | TestResult.xml 35 | 36 | # Build Results of an ATL Project 37 | [Dd]ebugPS/ 38 | [Rr]eleasePS/ 39 | dlldata.c 40 | 41 | *_i.c 42 | *_p.c 43 | *_i.h 44 | *.ilk 45 | *.meta 46 | *.obj 47 | *.pch 48 | *.pdb 49 | *.pgc 50 | *.pgd 51 | *.rsp 52 | *.sbr 53 | *.tlb 54 | *.tli 55 | *.tlh 56 | *.tmp 57 | *.tmp_proj 58 | *.log 59 | *.vspscc 60 | *.vssscc 61 | .builds 62 | *.pidb 63 | *.svclog 64 | *.scc 65 | 66 | # Chutzpah Test files 67 | _Chutzpah* 68 | 69 | # Visual C++ cache files 70 | ipch/ 71 | *.aps 72 | *.ncb 73 | *.opensdf 74 | *.sdf 75 | *.cachefile 76 | 77 | # Visual Studio profiler 78 | *.psess 79 | *.vsp 80 | *.vspx 81 | 82 | # TFS 2012 Local Workspace 83 | $tf/ 84 | 85 | # Guidance Automation Toolkit 86 | *.gpState 87 | 88 | # ReSharper is a .NET coding add-in 89 | _ReSharper*/ 90 | *.[Rr]e[Ss]harper 91 | *.DotSettings.user 92 | 93 | # JustCode is a .NET coding addin-in 94 | .JustCode 95 | 96 | # TeamCity is a build add-in 97 | _TeamCity* 98 | 99 | # DotCover is a Code Coverage Tool 100 | *.dotCover 101 | 102 | # NCrunch 103 | _NCrunch_* 104 | .*crunch*.local.xml 105 | 106 | # MightyMoose 107 | *.mm.* 108 | AutoTest.Net/ 109 | 110 | # Web workbench (sass) 111 | .sass-cache/ 112 | 113 | # Installshield output folder 114 | [Ee]xpress/ 115 | 116 | # DocProject is a documentation generator add-in 117 | DocProject/buildhelp/ 118 | DocProject/Help/*.HxT 119 | DocProject/Help/*.HxC 120 | DocProject/Help/*.hhc 121 | DocProject/Help/*.hhk 122 | DocProject/Help/*.hhp 123 | DocProject/Help/Html2 124 | DocProject/Help/html 125 | 126 | # Click-Once directory 127 | publish/ 128 | 129 | # Publish Web Output 130 | *.[Pp]ublish.xml 131 | *.azurePubxml 132 | # TODO: Comment the next line if you want to checkin your web deploy settings 133 | # but database connection strings (with potential passwords) will be unencrypted 134 | *.pubxml 135 | *.publishproj 136 | 137 | # NuGet Packages 138 | *.nupkg 139 | # The packages folder can be ignored because of Package Restore 140 | **/packages/* 141 | # except build/, which is used as an MSBuild target. 142 | !**/packages/build/ 143 | # Uncomment if necessary however generally it will be regenerated when needed 144 | #!**/packages/repositories.config 145 | 146 | # Windows Azure Build Output 147 | csx/ 148 | *.build.csdef 149 | 150 | # Windows Store app package directory 151 | AppPackages/ 152 | 153 | # Others 154 | *.[Cc]ache 155 | ClientBin/ 156 | [Ss]tyle[Cc]op.* 157 | ~$* 158 | *~ 159 | *.dbmdl 160 | *.dbproj.schemaview 161 | *.pfx 162 | *.publishsettings 163 | node_modules/ 164 | bower_components/ 165 | 166 | # RIA/Silverlight projects 167 | Generated_Code/ 168 | 169 | # Backup & report files from converting an old project file 170 | # to a newer Visual Studio version. Backup files are not needed, 171 | # because we have git ;-) 172 | _UpgradeReport_Files/ 173 | Backup*/ 174 | UpgradeLog*.XML 175 | UpgradeLog*.htm 176 | 177 | # SQL Server files 178 | *.mdf 179 | *.ldf 180 | 181 | # Business Intelligence projects 182 | *.rdl.data 183 | *.bim.layout 184 | *.bim_*.settings 185 | 186 | # Microsoft Fakes 187 | FakesAssemblies/ 188 | 189 | # Node.js Tools for Visual Studio 190 | .ntvs_analysis.dat 191 | 192 | # Visual Studio 6 build log 193 | *.plg 194 | 195 | # Visual Studio 6 workspace options file 196 | *.opt 197 | -------------------------------------------------------------------------------- /src/AppSecInc.ProcessDomain.Sample/AppSecInc.ProcessDomain.Sample.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Debug 5 | AnyCPU 6 | 9.0.21022 7 | 2.0 8 | {AA4C3A45-921B-422F-ABFF-B1848686596D} 9 | Exe 10 | Properties 11 | AppSecInc.ProcessDomain.Sample 12 | AppSecInc.ProcessDomain.Sample 13 | v3.5 14 | 512 15 | 16 | 17 | 18 | 19 | 3.5 20 | publish\ 21 | true 22 | Disk 23 | false 24 | Foreground 25 | 7 26 | Days 27 | false 28 | false 29 | true 30 | 0 31 | 1.0.0.%2a 32 | false 33 | false 34 | true 35 | ..\..\ 36 | true 37 | 38 | 39 | true 40 | full 41 | false 42 | bin\Debug\ 43 | DEBUG;TRACE 44 | prompt 45 | 4 46 | 47 | 48 | pdbonly 49 | true 50 | bin\Release\ 51 | TRACE 52 | prompt 53 | 4 54 | 55 | 56 | 57 | 58 | 59 | 3.5 60 | 61 | 62 | 3.5 63 | 64 | 65 | 3.5 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | {D69AD209-D78A-4091-9F5F-4D7562D66223} 82 | AppSecInc.ProcessDomain 83 | 84 | 85 | 86 | 87 | False 88 | .NET Framework 3.5 SP1 Client Profile 89 | false 90 | 91 | 92 | False 93 | .NET Framework 3.5 SP1 94 | true 95 | 96 | 97 | 98 | 99 | 2.0.12 100 | 101 | 102 | 103 | 104 | 111 | -------------------------------------------------------------------------------- /src/AppSecInc.ProcessDomain.UnitTests/AppSecInc.ProcessDomain.UnitTests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | 9.0.21022 8 | 2.0 9 | {6B78A648-1147-4D39-B125-7F07D09B649C} 10 | Library 11 | Properties 12 | AppSecInc.ProcessDomain.UnitTests 13 | AppSecInc.ProcessDomain.UnitTests 14 | v3.5 15 | 512 16 | 17 | 18 | 19 | 20 | 21 | 22 | 3.5 23 | ..\..\ 24 | true 25 | 26 | 27 | 28 | 29 | 30 | true 31 | full 32 | false 33 | bin\Debug\ 34 | DEBUG;TRACE 35 | prompt 36 | 4 37 | 38 | 39 | pdbonly 40 | true 41 | bin\Release\ 42 | TRACE 43 | prompt 44 | 4 45 | 46 | 47 | 48 | False 49 | ..\..\packages\NUnit.2.6.1\lib\nunit.framework.dll 50 | 51 | 52 | 53 | 54 | 3.5 55 | 56 | 57 | 3.5 58 | 59 | 60 | 3.5 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | {81E2FA83-26A1-4BE3-8F38-2C7327EC7840} 73 | AppSecInc.ProcessDomain.Host 74 | 75 | 76 | {D69AD209-D78A-4091-9F5F-4D7562D66223} 77 | AppSecInc.ProcessDomain 78 | 79 | 80 | 81 | 82 | 83 | 84 | PreserveNewest 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. 93 | 94 | 95 | 96 | 103 | -------------------------------------------------------------------------------- /src/AppSecInc.ProcessDomain/ProcessDomain.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using System.Reflection; 4 | using System.Security.Policy; 5 | using System.Threading; 6 | 7 | using log4net; 8 | 9 | using AppSecInc.ProcessDomain.Remoting; 10 | 11 | namespace AppSecInc.ProcessDomain 12 | { 13 | /// 14 | /// Represents an isolated environment in a separate process in which objects can be created and invoked 15 | /// 16 | public sealed class ProcessDomain : IActivation, IDisposable 17 | { 18 | static readonly ILog Logger = LogManager.GetLogger(typeof(ProcessDomain)); 19 | 20 | public event AttachedDelegate Attached; 21 | public event DetachedDelegate Detached; 22 | 23 | readonly AutoResetEvent _attachedEvent = new AutoResetEvent(false); 24 | readonly AutoResetEvent _detachedEvent = new AutoResetEvent(false); 25 | readonly string _friendlyName; 26 | readonly ActivatorProcess _process; 27 | 28 | int _unloaded; 29 | 30 | private ProcessDomain(string friendlyName, ProcessDomainSetup setupInfo) 31 | { 32 | _friendlyName = friendlyName; 33 | _process = new ActivatorProcess(friendlyName, setupInfo); 34 | _process.Attached += Process_Attached; 35 | _process.Detached += Process_Detached; 36 | _process.Start(); 37 | } 38 | 39 | private void Process_Attached() 40 | { 41 | var tmp = Attached; 42 | if (tmp != null) 43 | { 44 | Attached(); 45 | } 46 | _attachedEvent.Set(); 47 | } 48 | 49 | private void Process_Detached() 50 | { 51 | var tmp = Detached; 52 | if (tmp != null) 53 | { 54 | Detached(); 55 | } 56 | _detachedEvent.Set(); 57 | } 58 | 59 | /// 60 | /// Creates a ProcessDomain which allows hosting objects and code out-of-process 61 | /// 62 | /// The friendly name of the process domain which directly will also be the file name of the remote process 63 | /// Additional settings for creating the process domain 64 | public static ProcessDomain CreateDomain(string friendlyName, ProcessDomainSetup setupInfo) 65 | { 66 | Logger.InfoFormat("Creating process domain '{0}'", friendlyName); 67 | return new ProcessDomain(friendlyName, setupInfo); 68 | } 69 | 70 | /// 71 | /// Creates a ProcessDomain which allows hosting objects and code out-of-process 72 | /// 73 | /// The friendly name of the process domain which directly will also be the file name of the remote process 74 | public static ProcessDomain CreateDomain(string friendlyName) 75 | { 76 | return CreateDomain(friendlyName, new ProcessDomainSetup()); 77 | } 78 | 79 | /// 80 | /// Unloads a given process domain by terminating the process 81 | /// 82 | /// The process domain to unload 83 | public static void Unload(ProcessDomain domain) 84 | { 85 | Logger.InfoFormat("Unloading process domain '{0}'", domain._friendlyName); 86 | domain.Unload(); 87 | } 88 | 89 | /// 90 | /// Creates an object of the specified type 91 | /// 92 | public object CreateInstanceAndUnwrap(string assemblyName, string typeName) 93 | { 94 | return _process.Activator.CreateInstanceAndUnwrap(assemblyName, typeName); 95 | } 96 | 97 | /// 98 | /// Creates an object of the specified type 99 | /// 100 | public object CreateInstanceAndUnwrap(string assemblyName, string typeName, object[] activationAttributes) 101 | { 102 | return _process.Activator.CreateInstanceAndUnwrap(assemblyName, typeName, activationAttributes); 103 | } 104 | 105 | /// 106 | /// Creates an object of the specified type 107 | /// 108 | public object CreateInstanceAndUnwrap(string assemblyName, string typeName, bool ignoreCase, BindingFlags bindingAttr, Binder binder, object[] args, CultureInfo culture, object[] activationAttributes, Evidence securityAttributes) 109 | { 110 | return _process.Activator.CreateInstanceAndUnwrap(assemblyName, typeName, ignoreCase, bindingAttr, binder, args, culture, activationAttributes, securityAttributes); 111 | } 112 | 113 | /// 114 | /// Terminates the process, and then right after that it restarts again. 115 | /// 116 | public void Terminate() 117 | { 118 | if (_process != null) 119 | { 120 | _process.Terminate(); 121 | } 122 | } 123 | 124 | private void Unload() 125 | { 126 | if (Interlocked.CompareExchange(ref _unloaded, 1, 0) == 1) 127 | return; 128 | 129 | if (_process != null) 130 | { 131 | _process.Kill(); 132 | _process.Dispose(); 133 | } 134 | } 135 | 136 | ~ProcessDomain() 137 | { 138 | Unload(); 139 | } 140 | 141 | void IDisposable.Dispose() 142 | { 143 | Unload(); 144 | } 145 | } 146 | } -------------------------------------------------------------------------------- /ProcessDomain.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 2013 4 | VisualStudioVersion = 12.0.21005.1 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AppSecInc.ProcessDomain", "src\AppSecInc.ProcessDomain\AppSecInc.ProcessDomain.csproj", "{D69AD209-D78A-4091-9F5F-4D7562D66223}" 7 | ProjectSection(ProjectDependencies) = postProject 8 | {81E2FA83-26A1-4BE3-8F38-2C7327EC7840} = {81E2FA83-26A1-4BE3-8F38-2C7327EC7840} 9 | EndProjectSection 10 | EndProject 11 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AppSecInc.ProcessDomain.UnitTests", "src\AppSecInc.ProcessDomain.UnitTests\AppSecInc.ProcessDomain.UnitTests.csproj", "{6B78A648-1147-4D39-B125-7F07D09B649C}" 12 | EndProject 13 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AppSecInc.ProcessDomain.Host", "src\AppSecInc.ProcessDomain.Host\AppSecInc.ProcessDomain.Host.csproj", "{81E2FA83-26A1-4BE3-8F38-2C7327EC7840}" 14 | EndProject 15 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AppSecInc.ProcessDomain.Sample", "src\AppSecInc.ProcessDomain.Sample\AppSecInc.ProcessDomain.Sample.csproj", "{AA4C3A45-921B-422F-ABFF-B1848686596D}" 16 | EndProject 17 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Build", "Build", "{1FA63DD9-AED9-4C59-9037-CBCBCA352871}" 18 | ProjectSection(SolutionItems) = preProject 19 | .gitignore = .gitignore 20 | build.cmd = build.cmd 21 | LICENSE = LICENSE 22 | ProcessDomain.proj = ProcessDomain.proj 23 | README.md = README.md 24 | Version.proj = Version.proj 25 | EndProjectSection 26 | EndProject 27 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".nuget", ".nuget", "{92399DD0-ABB6-4510-96E0-8AD536B0AA88}" 28 | ProjectSection(SolutionItems) = preProject 29 | .nuget\NuGet.Config = .nuget\NuGet.Config 30 | .nuget\NuGet.exe = .nuget\NuGet.exe 31 | .nuget\NuGet.targets = .nuget\NuGet.targets 32 | .nuget\packages.config = .nuget\packages.config 33 | EndProjectSection 34 | EndProject 35 | Global 36 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 37 | Debug|Any CPU = Debug|Any CPU 38 | Debug|Mixed Platforms = Debug|Mixed Platforms 39 | Debug|Win32 = Debug|Win32 40 | Release|Any CPU = Release|Any CPU 41 | Release|Mixed Platforms = Release|Mixed Platforms 42 | Release|Win32 = Release|Win32 43 | EndGlobalSection 44 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 45 | {D69AD209-D78A-4091-9F5F-4D7562D66223}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 46 | {D69AD209-D78A-4091-9F5F-4D7562D66223}.Debug|Any CPU.Build.0 = Debug|Any CPU 47 | {D69AD209-D78A-4091-9F5F-4D7562D66223}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU 48 | {D69AD209-D78A-4091-9F5F-4D7562D66223}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU 49 | {D69AD209-D78A-4091-9F5F-4D7562D66223}.Debug|Win32.ActiveCfg = Debug|Any CPU 50 | {D69AD209-D78A-4091-9F5F-4D7562D66223}.Release|Any CPU.ActiveCfg = Release|Any CPU 51 | {D69AD209-D78A-4091-9F5F-4D7562D66223}.Release|Any CPU.Build.0 = Release|Any CPU 52 | {D69AD209-D78A-4091-9F5F-4D7562D66223}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU 53 | {D69AD209-D78A-4091-9F5F-4D7562D66223}.Release|Mixed Platforms.Build.0 = Release|Any CPU 54 | {D69AD209-D78A-4091-9F5F-4D7562D66223}.Release|Win32.ActiveCfg = Release|Any CPU 55 | {6B78A648-1147-4D39-B125-7F07D09B649C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 56 | {6B78A648-1147-4D39-B125-7F07D09B649C}.Debug|Any CPU.Build.0 = Debug|Any CPU 57 | {6B78A648-1147-4D39-B125-7F07D09B649C}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU 58 | {6B78A648-1147-4D39-B125-7F07D09B649C}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU 59 | {6B78A648-1147-4D39-B125-7F07D09B649C}.Debug|Win32.ActiveCfg = Debug|Any CPU 60 | {6B78A648-1147-4D39-B125-7F07D09B649C}.Release|Any CPU.ActiveCfg = Release|Any CPU 61 | {6B78A648-1147-4D39-B125-7F07D09B649C}.Release|Any CPU.Build.0 = Release|Any CPU 62 | {6B78A648-1147-4D39-B125-7F07D09B649C}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU 63 | {6B78A648-1147-4D39-B125-7F07D09B649C}.Release|Mixed Platforms.Build.0 = Release|Any CPU 64 | {6B78A648-1147-4D39-B125-7F07D09B649C}.Release|Win32.ActiveCfg = Release|Any CPU 65 | {81E2FA83-26A1-4BE3-8F38-2C7327EC7840}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 66 | {81E2FA83-26A1-4BE3-8F38-2C7327EC7840}.Debug|Any CPU.Build.0 = Debug|Any CPU 67 | {81E2FA83-26A1-4BE3-8F38-2C7327EC7840}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU 68 | {81E2FA83-26A1-4BE3-8F38-2C7327EC7840}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU 69 | {81E2FA83-26A1-4BE3-8F38-2C7327EC7840}.Debug|Win32.ActiveCfg = Debug|Any CPU 70 | {81E2FA83-26A1-4BE3-8F38-2C7327EC7840}.Release|Any CPU.ActiveCfg = Release|Any CPU 71 | {81E2FA83-26A1-4BE3-8F38-2C7327EC7840}.Release|Any CPU.Build.0 = Release|Any CPU 72 | {81E2FA83-26A1-4BE3-8F38-2C7327EC7840}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU 73 | {81E2FA83-26A1-4BE3-8F38-2C7327EC7840}.Release|Mixed Platforms.Build.0 = Release|Any CPU 74 | {81E2FA83-26A1-4BE3-8F38-2C7327EC7840}.Release|Win32.ActiveCfg = Release|Any CPU 75 | {AA4C3A45-921B-422F-ABFF-B1848686596D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 76 | {AA4C3A45-921B-422F-ABFF-B1848686596D}.Debug|Any CPU.Build.0 = Debug|Any CPU 77 | {AA4C3A45-921B-422F-ABFF-B1848686596D}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU 78 | {AA4C3A45-921B-422F-ABFF-B1848686596D}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU 79 | {AA4C3A45-921B-422F-ABFF-B1848686596D}.Debug|Win32.ActiveCfg = Debug|Any CPU 80 | {AA4C3A45-921B-422F-ABFF-B1848686596D}.Release|Any CPU.ActiveCfg = Release|Any CPU 81 | {AA4C3A45-921B-422F-ABFF-B1848686596D}.Release|Any CPU.Build.0 = Release|Any CPU 82 | {AA4C3A45-921B-422F-ABFF-B1848686596D}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU 83 | {AA4C3A45-921B-422F-ABFF-B1848686596D}.Release|Mixed Platforms.Build.0 = Release|Any CPU 84 | {AA4C3A45-921B-422F-ABFF-B1848686596D}.Release|Win32.ActiveCfg = Release|Any CPU 85 | EndGlobalSection 86 | GlobalSection(SolutionProperties) = preSolution 87 | HideSolutionNode = FALSE 88 | EndGlobalSection 89 | EndGlobal 90 | -------------------------------------------------------------------------------- /src/AppSecInc.ProcessDomain.Sample/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.Linq; 4 | using System.Threading; 5 | using AppSecInc.ProcessDomain.Utils; 6 | 7 | namespace AppSecInc.ProcessDomain.Sample 8 | { 9 | public class Program 10 | { 11 | static void Main(string[] args) 12 | { 13 | try 14 | { 15 | var localObject = new RemoteObject(); 16 | Console.WriteLine("Local object - Main Module Location: {0}", localObject.GetProcessMainModuleFileName()); 17 | Console.WriteLine("Local object - App.config location: {0}", localObject.GetAppConfigLocation()); 18 | Console.WriteLine("Local object - App.config value: {0}", localObject.GetAppConfigValue("MyString")); 19 | 20 | var setup = new ProcessDomainSetup 21 | { 22 | ProcessStartTimeout = new TimeSpan(0, 0, 5), 23 | }; 24 | 25 | using (ProcessDomain processDomain = ProcessDomain.CreateDomain("RemoteProcess", setup)) 26 | { 27 | LoggingConfigurator.CreateConfigurator(processDomain).ConfigureAppConfig(); 28 | 29 | var remoteObject = (RemoteObject)processDomain.CreateInstanceAndUnwrap(typeof(RemoteObject).Assembly.FullName, typeof(RemoteObject).FullName); 30 | Console.WriteLine("Remote object - Main Module Location: {0}", remoteObject.GetProcessMainModuleFileName()); 31 | Console.WriteLine("Remote object - App.config location: {0}", remoteObject.GetAppConfigLocation()); 32 | Console.WriteLine("Remote object - App.config value: {0}", remoteObject.GetAppConfigValue("MyString")); 33 | 34 | var detachedEvent = new ManualResetEvent(false); 35 | var attachedEvent = new ManualResetEvent(false); 36 | processDomain.Detached += () => detachedEvent.Set(); 37 | processDomain.Attached += () => 38 | { 39 | LoggingConfigurator.CreateConfigurator(processDomain).ConfigureAppConfig(); 40 | attachedEvent.Set(); 41 | }; 42 | 43 | Console.WriteLine("Finding RemoteProcess and killing it..."); 44 | Process.GetProcessesByName("RemoteProcess").FirstOrDefault().Kill(); 45 | 46 | if (!detachedEvent.WaitOne(10000)) 47 | { 48 | throw new Exception("Timed-out while waiting for process to die"); 49 | } 50 | 51 | Console.WriteLine("Waiting for new process to spawn"); 52 | if (!attachedEvent.WaitOne(10000)) 53 | { 54 | throw new Exception("Timed-out while waiting for process to restart"); 55 | } 56 | 57 | Console.WriteLine("Re-creating remote object in newly spawned process"); 58 | remoteObject = (RemoteObject)processDomain.CreateInstanceAndUnwrap(typeof(RemoteObject).Assembly.FullName, typeof(RemoteObject).FullName); 59 | Console.WriteLine("Remote object - Main Module Location: {0}", remoteObject.GetProcessMainModuleFileName()); 60 | Console.WriteLine("Remote object - App.config location: {0}", remoteObject.GetAppConfigLocation()); 61 | Console.WriteLine("Remote object - App.config value: {0}", remoteObject.GetAppConfigValue("MyString")); 62 | 63 | Console.WriteLine("Throwing an exception..."); 64 | try 65 | { 66 | remoteObject.ThrowException(); 67 | Console.WriteLine("Did not catch an exception..."); 68 | } 69 | catch (RemoteException rex) 70 | { 71 | Console.WriteLine("Caught exception: {0}", rex.Message); 72 | } 73 | } 74 | 75 | Console.WriteLine("Two process domains at the same time"); 76 | 77 | using (var processDomain1 = ProcessDomain.CreateDomain("RemoteProcess1", setup)) 78 | using (var processDomain2 = ProcessDomain.CreateDomain("RemoteProcess2", setup)) 79 | { 80 | var remoteObject1 = (RemoteObject)processDomain1.CreateInstanceAndUnwrap(typeof(RemoteObject).Assembly.FullName, typeof(RemoteObject).FullName); 81 | var remoteObject2 = (RemoteObject)processDomain2.CreateInstanceAndUnwrap(typeof(RemoteObject).Assembly.FullName, typeof(RemoteObject).FullName); 82 | 83 | Console.WriteLine("Remote object #1 - App.config value: {0}", remoteObject1.GetAppConfigValue("MyString")); 84 | Console.WriteLine("Remote object #2 - App.config value: {0}", remoteObject2.GetAppConfigValue("MyString")); 85 | } 86 | 87 | Console.WriteLine("Process domain in alternate location"); 88 | setup.AppDomainSetupInformation.ApplicationBase = @"c:\"; 89 | setup.ExternalAssemblies[typeof(Program).Assembly.GetName()] = typeof(Program).Assembly.Location; 90 | using (var processDomain = ProcessDomain.CreateDomain("RemoteProcess", setup)) 91 | { 92 | var remoteObject = (RemoteObject)processDomain.CreateInstanceAndUnwrap(typeof(RemoteObject).Assembly.FullName, typeof(RemoteObject).FullName); 93 | 94 | Console.WriteLine("Remote object - App.config value: {0}", remoteObject.GetAppConfigValue("MyString")); 95 | } 96 | } 97 | catch (Exception ex) 98 | { 99 | Console.WriteLine("Exception: {0}", ex); 100 | } 101 | 102 | Console.WriteLine("Press any key to exit"); 103 | Console.ReadKey(); 104 | } 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/AppSecInc.ProcessDomain/AppSecInc.ProcessDomain.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Debug 5 | AnyCPU 6 | 9.0.30729 7 | 2.0 8 | {D69AD209-D78A-4091-9F5F-4D7562D66223} 9 | Library 10 | Properties 11 | AppSecInc.ProcessDomain 12 | AppSecInc.ProcessDomain 13 | v2.0 14 | 512 15 | false 16 | 17 | 18 | 19 | 20 | 21 | 22 | 3.5 23 | publish\ 24 | true 25 | Disk 26 | false 27 | Foreground 28 | 7 29 | Days 30 | false 31 | false 32 | true 33 | 0 34 | 1.0.0.%2a 35 | false 36 | false 37 | true 38 | ..\..\ 39 | true 40 | 41 | 42 | true 43 | full 44 | false 45 | ..\..\Build\Debug\ 46 | DEBUG;TRACE 47 | prompt 48 | 4 49 | 50 | 51 | pdbonly 52 | true 53 | ..\..\Build\Release\ 54 | TRACE 55 | prompt 56 | 4 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | Properties\GlobalAssemblyInfo.cs 67 | 68 | 69 | 70 | 71 | 72 | True 73 | True 74 | Resources.resx 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | ResXFileCodeGenerator 95 | Resources.Designer.cs 96 | Designer 97 | 98 | 99 | 100 | 101 | False 102 | .NET Framework 3.5 SP1 Client Profile 103 | false 104 | 105 | 106 | False 107 | .NET Framework 3.5 SP1 108 | true 109 | 110 | 111 | 112 | 113 | 2.0.12 114 | 115 | 116 | 117 | 124 | 125 | 126 | 127 | 128 | 129 | -------------------------------------------------------------------------------- /src/AppSecInc.ProcessDomain/Properties/Resources.resx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | 121 | 122 | ..\Resources\Program.cs;System.String, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;utf-8 123 | 124 | -------------------------------------------------------------------------------- /src/AppSecInc.ProcessDomain/Remoting/ActivatorHost.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.ComponentModel; 4 | using System.Diagnostics; 5 | using System.IO; 6 | using System.Reflection; 7 | using System.Runtime.Remoting; 8 | using System.Runtime.Remoting.Channels; 9 | using System.Runtime.Remoting.Channels.Ipc; 10 | using System.Threading; 11 | 12 | namespace AppSecInc.ProcessDomain.Remoting 13 | { 14 | /// 15 | /// This class hosts an Activator in a new process on an IPC channel 16 | /// 17 | [Serializable] 18 | internal class ActivatorHost : MarshalByRefObject 19 | { 20 | public const string ServerChannelName = "ProcessDomainServer_{0}"; 21 | public const string ClientChannelName = "ProcessDomainClient_{0}"; 22 | public const string EventName = "ProcessDomainEvent_{0}"; 23 | public const string ActivatorName = "Activator"; 24 | 25 | readonly Process _process; 26 | readonly IpcChannel _channel; 27 | 28 | public ActivatorHost(string guid, int processId, ProcessDomainSetup setup) 29 | { 30 | SetupDllDirectories(setup); 31 | var serverProvider = new BinaryServerFormatterSinkProvider { TypeFilterLevel = setup.TypeFilterLevel }; 32 | var clientProvider = new BinaryClientFormatterSinkProvider(); 33 | _process = Process.GetProcessById(processId); 34 | 35 | var properties = new Hashtable(); 36 | properties["portName"] = string.Format(ServerChannelName, guid); 37 | properties["name"] = string.Format(ServerChannelName, guid); 38 | properties["rejectRemoteRequests"] = true; 39 | setup.Remoting.ApplyServerProperties(properties); 40 | 41 | _channel = new IpcChannel(properties, clientProvider, serverProvider); 42 | ChannelServices.RegisterChannel(_channel, false); 43 | RemotingConfiguration.RegisterWellKnownServiceType(typeof(Activator), ActivatorName, WellKnownObjectMode.Singleton); 44 | 45 | EventWaitHandle serverStartedHandle = null; 46 | try 47 | { 48 | bool created; 49 | serverStartedHandle = new EventWaitHandle(false, EventResetMode.ManualReset, string.Format(EventName, guid), out created); 50 | 51 | if (created) 52 | { 53 | throw new Exception("Event handle did not exist for remote process"); 54 | } 55 | 56 | serverStartedHandle.Set(); 57 | } 58 | finally 59 | { 60 | if (serverStartedHandle != null) 61 | { 62 | serverStartedHandle.Close(); 63 | } 64 | } 65 | } 66 | 67 | /// 68 | /// Waits for the parent process to exit 69 | /// 70 | public void WaitForExit() 71 | { 72 | _process.WaitForExit(); 73 | } 74 | 75 | /// 76 | /// Runs the Activator Host and blocks until the parent process exits 77 | /// 78 | public static void Run(string[] args) 79 | { 80 | // args[0] = process domain assembly path 81 | // args[1] = guid 82 | // args[2] = parent process id 83 | // args[3] = ProcessDomainSetup file 84 | 85 | if (args.Length != 4) 86 | { 87 | return; 88 | } 89 | 90 | string friendlyName = Path.GetFileNameWithoutExtension(Assembly.GetEntryAssembly().Location); 91 | string guid = args[1]; 92 | int processId = int.Parse(args[2]); 93 | 94 | var domainSetup = ProcessDomainSetup.Deserialize(args[3]); 95 | 96 | var domain = AppDomain.CreateDomain(friendlyName, domainSetup.Evidence, domainSetup.AppDomainSetupInformation); 97 | 98 | var type = Assembly.GetEntryAssembly().GetType("AppSecInc.ProcessDomain.AssemblyResolver"); 99 | 100 | if (type == null) 101 | { 102 | throw new TypeLoadException("Could not load type for assembly resolver"); 103 | } 104 | 105 | // add ProcessDomain assembly to resolver 106 | if (domainSetup.ExternalAssemblies == null) 107 | { 108 | domainSetup.ExternalAssemblies = new System.Collections.Generic.Dictionary(); 109 | } 110 | domainSetup.ExternalAssemblies[typeof(ActivatorHost).Assembly.GetName()] = typeof(ActivatorHost).Assembly.Location; 111 | 112 | var resolver = domain.CreateInstanceFromAndUnwrap(type.Assembly.Location, 113 | type.FullName, 114 | false, 115 | BindingFlags.CreateInstance | BindingFlags.Public | BindingFlags.Instance, 116 | null, 117 | new[] { domainSetup.ExternalAssemblies }, 118 | null, null, null); 119 | 120 | type.InvokeMember("Setup", BindingFlags.InvokeMethod | BindingFlags.Instance | BindingFlags.Public, null, resolver, null); 121 | 122 | var host = (ActivatorHost) domain.CreateInstanceFromAndUnwrap(typeof (ActivatorHost).Assembly.Location, 123 | typeof (ActivatorHost).FullName, 124 | false, 125 | BindingFlags.CreateInstance | BindingFlags.Public | BindingFlags.Instance, 126 | null, 127 | new object[] {guid, processId, domainSetup}, 128 | null, null, null); 129 | 130 | host.WaitForExit(); 131 | 132 | type.InvokeMember("TearDown", BindingFlags.InvokeMethod | BindingFlags.Instance | BindingFlags.Public, null, resolver, null); 133 | 134 | // If parent process (host) finishes, the current process must end. 135 | Environment.Exit(0); 136 | } 137 | 138 | private static void SetupDllDirectories(ProcessDomainSetup setup) 139 | { 140 | if (!string.IsNullOrEmpty(setup.DllDirectory)) 141 | { 142 | if (!WinApi.SetDllDirectory(setup.DllDirectory)) 143 | { 144 | throw new Win32Exception(); 145 | } 146 | } 147 | } 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /src/AppSecInc.ProcessDomain/Resources/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.IO; 5 | using System.Reflection; 6 | 7 | /* 8 | * This file contains source code that will get compiled at run-time in order to create a 9 | * hosting process. It should remain as light-weight as possible, while the bulk of the 10 | * work should be done in the created type 'ActivatorHost'. It should also not have any 11 | * references to assemblies not in the GAC, as this program will fail to load because the 12 | * assembly resolver won't have a chance to get configured to find assemblies in a different 13 | * location than the application domain's Base Directory. 14 | */ 15 | 16 | [assembly: AssemblyTitle("ProcessDomain Host")] 17 | [assembly: AssemblyDescription("Provides an isolated environment for creating objects and executing code in another process")] 18 | 19 | namespace AppSecInc.ProcessDomain 20 | { 21 | public class Program 22 | { 23 | static void Main(string[] args) 24 | { 25 | if (args.Length < 1) 26 | { 27 | Log("Invalid arguments"); 28 | return; 29 | } 30 | 31 | try 32 | { 33 | var resolveMap = new Dictionary 34 | { 35 | { new AssemblyName("${ProcessDomainAssemblyName}"), args[0] } 36 | }; 37 | var resolver = new AssemblyResolver(resolveMap); 38 | resolver.Setup(); 39 | 40 | Type hostType = Type.GetType("${ActivatorHostTypeName}"); 41 | 42 | if (hostType == null) 43 | { 44 | throw new TypeLoadException(string.Format("Could not load ActivatorHost type '${ActivatorHostTypeName}' using resolver with '${ProcessDomainAssemblyName}' mapped to '{0}'", args[0])); 45 | } 46 | 47 | var methodInfo = hostType.GetMethod("Run", BindingFlags.Static | BindingFlags.Public, null, new[] { typeof(string[]) }, null); 48 | 49 | if(methodInfo == null) 50 | { 51 | throw new Exception("'Run' method on ActivatorHost not found."); 52 | } 53 | 54 | methodInfo.Invoke(null, new[] { args }); 55 | } 56 | catch (Exception ex) 57 | { 58 | Log("Failed to launch Activator Host: {0}", ex); 59 | } 60 | } 61 | 62 | // Just very basic logging. Any extra logging configuration should be done by creating 63 | // the logging configurator object in the new process domain 64 | static StreamWriter _logFile; 65 | static void OpenLogFile() 66 | { 67 | string fileName = string.Format("{0}-{1}.log", Assembly.GetEntryAssembly().Location, Process.GetCurrentProcess().Id); 68 | try 69 | { 70 | _logFile = new StreamWriter(fileName, false); 71 | } 72 | catch(Exception ex) 73 | { 74 | Console.WriteLine("Failed to open process domain bootstrap log: {0}", ex); 75 | } 76 | } 77 | 78 | static void Log(string message, params object[] args) 79 | { 80 | if (_logFile == null) 81 | { 82 | OpenLogFile(); 83 | } 84 | 85 | if (_logFile != null) 86 | { 87 | _logFile.WriteLine(string.Format("[{0}] {1}", DateTime.Now, message), args); 88 | _logFile.Flush(); 89 | } 90 | } 91 | } 92 | 93 | public class AssemblyResolver : MarshalByRefObject 94 | { 95 | // Prevent garbage collection 96 | public override object InitializeLifetimeService() 97 | { 98 | return null; 99 | } 100 | 101 | private readonly Dictionary> _mapByName; 102 | 103 | public AssemblyResolver(Dictionary map) 104 | { 105 | _mapByName = new Dictionary>(); 106 | 107 | if (map != null) 108 | { 109 | foreach (var entry in map) 110 | { 111 | Dictionary subMap; 112 | if (!_mapByName.TryGetValue(entry.Key.Name, out subMap)) 113 | { 114 | _mapByName[entry.Key.Name] = subMap = new Dictionary(); 115 | } 116 | 117 | subMap[entry.Key] = entry.Value; 118 | } 119 | } 120 | } 121 | 122 | public void Setup() 123 | { 124 | AppDomain.CurrentDomain.AssemblyResolve += Resolve; 125 | AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve += ReflectionOnlyResolve; 126 | } 127 | 128 | public void TearDown() 129 | { 130 | AppDomain.CurrentDomain.AssemblyResolve -= Resolve; 131 | AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve -= ReflectionOnlyResolve; 132 | } 133 | 134 | private Assembly Resolve(object sender, ResolveEventArgs args) 135 | { 136 | var assemblyFile = FindAssemblyName(args.Name); 137 | if (assemblyFile != null) 138 | { 139 | return Assembly.LoadFrom(assemblyFile); 140 | } 141 | 142 | return null; 143 | } 144 | 145 | private Assembly ReflectionOnlyResolve(object sender, ResolveEventArgs args) 146 | { 147 | var assemblyFile = FindAssemblyName(args.Name); 148 | if (assemblyFile != null) 149 | { 150 | return Assembly.ReflectionOnlyLoadFrom(assemblyFile); 151 | } 152 | 153 | return null; 154 | } 155 | 156 | private static bool PublicKeysTokenEqual(byte[] lhs, byte[] rhs) 157 | { 158 | if (lhs == null || rhs == null) 159 | { 160 | return lhs == rhs; 161 | } 162 | 163 | if (lhs.Length != rhs.Length) 164 | return false; 165 | 166 | for (int i = 0; i < lhs.Length; i++) 167 | { 168 | if (lhs[i] != rhs[i]) 169 | return false; 170 | } 171 | 172 | return true; 173 | } 174 | 175 | private string FindAssemblyName(string name) 176 | { 177 | var assemblyName = new AssemblyName(name); 178 | Dictionary subMap; 179 | 180 | if (!_mapByName.TryGetValue(assemblyName.Name, out subMap)) 181 | { 182 | return null; 183 | } 184 | 185 | foreach (var entry in subMap) 186 | { 187 | // do weak assembly name matching, matching only values specified in assembly name 188 | if (assemblyName.Version != null && assemblyName.Version != entry.Key.Version) 189 | continue; 190 | 191 | if (assemblyName.CultureInfo != null && !assemblyName.CultureInfo.Equals(entry.Key.CultureInfo)) 192 | continue; 193 | 194 | if (!PublicKeysTokenEqual(assemblyName.GetPublicKeyToken(), entry.Key.GetPublicKeyToken())) 195 | continue; 196 | 197 | return entry.Value; 198 | } 199 | 200 | return null; 201 | } 202 | } 203 | } -------------------------------------------------------------------------------- /.nuget/NuGet.targets: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $(MSBuildProjectDirectory)\..\ 5 | 6 | 7 | false 8 | 9 | 10 | false 11 | 12 | 13 | true 14 | 15 | 16 | false 17 | 18 | 19 | 20 | 21 | 22 | 26 | 27 | 28 | 29 | 30 | $([System.IO.Path]::Combine($(SolutionDir), ".nuget")) 31 | 32 | 33 | 34 | 35 | $(SolutionDir).nuget 36 | 37 | 38 | 39 | $(MSBuildProjectDirectory)\packages.$(MSBuildProjectName.Replace(' ', '_')).config 40 | $(MSBuildProjectDirectory)\packages.$(MSBuildProjectName).config 41 | 42 | 43 | 44 | $(MSBuildProjectDirectory)\packages.config 45 | $(PackagesProjectConfig) 46 | 47 | 48 | 49 | 50 | $(NuGetToolsPath)\NuGet.exe 51 | @(PackageSource) 52 | 53 | "$(NuGetExePath)" 54 | mono --runtime=v4.0.30319 "$(NuGetExePath)" 55 | 56 | $(TargetDir.Trim('\\')) 57 | 58 | -RequireConsent 59 | -NonInteractive 60 | 61 | "$(SolutionDir) " 62 | "$(SolutionDir)" 63 | 64 | 65 | $(NuGetCommand) install "$(PackagesConfig)" -source "$(PackageSources)" $(NonInteractiveSwitch) $(RequireConsentSwitch) -solutionDir $(PaddedSolutionDir) 66 | $(NuGetCommand) pack "$(ProjectPath)" -Properties "Configuration=$(Configuration);Platform=$(Platform)" $(NonInteractiveSwitch) -OutputDirectory "$(PackageOutputDir)" -symbols 67 | 68 | 69 | 70 | RestorePackages; 71 | $(BuildDependsOn); 72 | 73 | 74 | 75 | 76 | $(BuildDependsOn); 77 | BuildPackage; 78 | 79 | 80 | 81 | 82 | 83 | 84 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 99 | 100 | 103 | 104 | 105 | 106 | 108 | 109 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 141 | 142 | 143 | 144 | 145 | -------------------------------------------------------------------------------- /src/AppSecInc.ProcessDomain/ProcessDomainSetup.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel; 4 | using System.Diagnostics; 5 | using System.IO; 6 | using System.Reflection; 7 | using System.Runtime.InteropServices; 8 | using System.Runtime.Serialization.Formatters; 9 | using System.Runtime.Serialization.Formatters.Binary; 10 | using System.Security.Policy; 11 | using System.Text; 12 | using AppSecInc.ProcessDomain.Remoting; 13 | 14 | namespace AppSecInc.ProcessDomain 15 | { 16 | /// 17 | /// Parameters for a process domain 18 | /// 19 | [Serializable] 20 | public class ProcessDomainSetup 21 | { 22 | public const string DefaultCompilerVersion = "v3.5"; 23 | 24 | public ProcessDomainSetup() 25 | { 26 | ExecutableDirectory = Path.GetTempPath(); 27 | ProcessStartTimeout = new TimeSpan(0, 0, 60); 28 | FileDeletionTimeout = new TimeSpan(0, 0, 10); 29 | DeleteOnUnload = true; 30 | RestartOnProcessExit = true; 31 | AppDomainSetupInformation = AppDomain.CurrentDomain.SetupInformation; 32 | WorkingDirectory = Environment.CurrentDirectory; 33 | Evidence = AppDomain.CurrentDomain.Evidence; 34 | TypeFilterLevel = TypeFilterLevel.Low; 35 | DllDirectory = CurrentDllDirectory; 36 | EnvironmentVariables = new Dictionary(); 37 | ExternalAssemblies = new Dictionary(); 38 | PriorityClass = ProcessPriorityClass.Normal; 39 | CompilerVersion = DefaultCompilerVersion; 40 | CopyConfigurationFile = false; 41 | Remoting = new RemotingOptions(); 42 | } 43 | 44 | /// 45 | /// Determines whether the AppDomainSetup configuration file has to be copied into assembly name dot config file. 46 | /// 47 | public bool CopyConfigurationFile { get; set; } 48 | 49 | /// 50 | /// Specifies the c# compiler version for the remote process 51 | /// 52 | public string CompilerVersion { get; set; } 53 | 54 | /// 55 | /// Specifies maximum time spent trying to delete a assembly from the disk. 56 | /// 57 | public TimeSpan FileDeletionTimeout 58 | { 59 | get; 60 | set; 61 | } 62 | 63 | /// 64 | /// Specifies where the temporary remote process executable file will be created 65 | /// 66 | public string ExecutableDirectory 67 | { 68 | get; 69 | set; 70 | } 71 | 72 | /// 73 | /// Specifies the working directory for the remote process 74 | /// 75 | public string WorkingDirectory 76 | { 77 | get; 78 | set; 79 | } 80 | 81 | /// 82 | /// Specifies a directory to invoke SetDllDirectory with to redirect DLL probing to the working directory 83 | /// 84 | public string DllDirectory 85 | { 86 | get; 87 | set; 88 | } 89 | 90 | /// 91 | /// Gets the currently configured DLL search path as set by SetDllDirectory 92 | /// 93 | 94 | public static string CurrentDllDirectory 95 | { 96 | get 97 | { 98 | int bytesNeeded = WinApi.GetDllDirectory(0, null); 99 | if (bytesNeeded == 0) 100 | { 101 | throw new Win32Exception(); 102 | } 103 | var sb = new StringBuilder(bytesNeeded); 104 | // reset the last error on this thread 105 | WinApi.SetLastError(0); 106 | bytesNeeded = WinApi.GetDllDirectory(bytesNeeded, sb); 107 | 108 | // does 0 mean failure or empty string? 109 | if (bytesNeeded == 0) 110 | { 111 | int errorCode = Marshal.GetLastWin32Error(); 112 | if (errorCode != 0) 113 | { 114 | throw new Win32Exception(errorCode); 115 | } 116 | } 117 | return sb.ToString(); 118 | } 119 | } 120 | 121 | /// 122 | /// Specifies how long to wait for the remote process to start 123 | /// 124 | public TimeSpan ProcessStartTimeout 125 | { 126 | get; 127 | set; 128 | } 129 | 130 | /// 131 | /// Specifies whether or not to delete the generated executable after the process domain has unloaded 132 | /// 133 | public bool DeleteOnUnload 134 | { 135 | get; 136 | set; 137 | } 138 | 139 | /// 140 | /// Specifices whether the process domain process should be relaunched should the process exit prematurely 141 | /// 142 | public bool RestartOnProcessExit 143 | { 144 | get; 145 | set; 146 | } 147 | 148 | /// 149 | /// Setup information for the AppDomain that the object will be created in, in the remote process. By default, this will be the 150 | /// current domain's setup information from which the proxy is being created 151 | /// 152 | public AppDomainSetup AppDomainSetupInformation 153 | { 154 | get; 155 | set; 156 | } 157 | 158 | /// 159 | /// Allows specifying which platform to compile the target remote process assembly for 160 | /// 161 | public PlatformTarget Platform 162 | { 163 | get; 164 | set; 165 | } 166 | 167 | /// 168 | /// Remote security policy 169 | /// 170 | public Evidence Evidence 171 | { 172 | get; 173 | set; 174 | } 175 | 176 | 177 | /// 178 | /// Remoting type filter level - controls how much functionality is exposed via remoting the process domain remotely 179 | /// 180 | public TypeFilterLevel TypeFilterLevel 181 | { 182 | get; 183 | set; 184 | } 185 | 186 | /// 187 | /// Environment variables of the remote process 188 | /// 189 | public Dictionary EnvironmentVariables 190 | { 191 | get; 192 | set; 193 | } 194 | 195 | /// 196 | /// A map of assembly names to assembly file locations that will need to be resolved inside the Process Domain 197 | /// 198 | public Dictionary ExternalAssemblies 199 | { 200 | get; 201 | set; 202 | } 203 | 204 | /// 205 | /// The priority to run the remote process at 206 | /// 207 | public ProcessPriorityClass PriorityClass 208 | { 209 | get; 210 | set; 211 | } 212 | 213 | /// 214 | /// Advanced remoting-related options 215 | /// 216 | public RemotingOptions Remoting { get; private set; } 217 | 218 | internal static void Serialize(ProcessDomainSetup setup, string filename) 219 | { 220 | var formatter = new BinaryFormatter(); 221 | 222 | using (var fs = new FileStream(filename, FileMode.Create, FileAccess.Write)) 223 | { 224 | formatter.Serialize(fs, setup); 225 | } 226 | } 227 | 228 | internal static ProcessDomainSetup Deserialize(string filename) 229 | { 230 | var formatter = new BinaryFormatter(); 231 | 232 | using (var fs = new FileStream(filename, FileMode.Open, FileAccess.Read)) 233 | { 234 | return (ProcessDomainSetup)formatter.Deserialize(fs); 235 | } 236 | } 237 | } 238 | } 239 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Eclipse Public License - v 1.0 2 | 3 | THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC 4 | LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM 5 | CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. 6 | 7 | 1. DEFINITIONS 8 | 9 | "Contribution" means: 10 | 11 | a) in the case of the initial Contributor, the initial code and documentation 12 | distributed under this Agreement, and 13 | b) in the case of each subsequent Contributor: 14 | i) changes to the Program, and 15 | ii) additions to the Program; 16 | 17 | where such changes and/or additions to the Program originate from and are 18 | distributed by that particular Contributor. A Contribution 'originates' 19 | from a Contributor if it was added to the Program by such Contributor 20 | itself or anyone acting on such Contributor's behalf. Contributions do not 21 | include additions to the Program which: (i) are separate modules of 22 | software distributed in conjunction with the Program under their own 23 | license agreement, and (ii) are not derivative works of the Program. 24 | 25 | "Contributor" means any person or entity that distributes the Program. 26 | 27 | "Licensed Patents" mean patent claims licensable by a Contributor which are 28 | necessarily infringed by the use or sale of its Contribution alone or when 29 | combined with the Program. 30 | 31 | "Program" means the Contributions distributed in accordance with this 32 | Agreement. 33 | 34 | "Recipient" means anyone who receives the Program under this Agreement, 35 | including all Contributors. 36 | 37 | 2. GRANT OF RIGHTS 38 | a) Subject to the terms of this Agreement, each Contributor hereby grants 39 | Recipient a non-exclusive, worldwide, royalty-free copyright license to 40 | reproduce, prepare derivative works of, publicly display, publicly 41 | perform, distribute and sublicense the Contribution of such Contributor, 42 | if any, and such derivative works, in source code and object code form. 43 | b) Subject to the terms of this Agreement, each Contributor hereby grants 44 | Recipient a non-exclusive, worldwide, royalty-free patent license under 45 | Licensed Patents to make, use, sell, offer to sell, import and otherwise 46 | transfer the Contribution of such Contributor, if any, in source code and 47 | object code form. This patent license shall apply to the combination of 48 | the Contribution and the Program if, at the time the Contribution is 49 | added by the Contributor, such addition of the Contribution causes such 50 | combination to be covered by the Licensed Patents. The patent license 51 | shall not apply to any other combinations which include the Contribution. 52 | No hardware per se is licensed hereunder. 53 | c) Recipient understands that although each Contributor grants the licenses 54 | to its Contributions set forth herein, no assurances are provided by any 55 | Contributor that the Program does not infringe the patent or other 56 | intellectual property rights of any other entity. Each Contributor 57 | disclaims any liability to Recipient for claims brought by any other 58 | entity based on infringement of intellectual property rights or 59 | otherwise. As a condition to exercising the rights and licenses granted 60 | hereunder, each Recipient hereby assumes sole responsibility to secure 61 | any other intellectual property rights needed, if any. For example, if a 62 | third party patent license is required to allow Recipient to distribute 63 | the Program, it is Recipient's responsibility to acquire that license 64 | before distributing the Program. 65 | d) Each Contributor represents that to its knowledge it has sufficient 66 | copyright rights in its Contribution, if any, to grant the copyright 67 | license set forth in this Agreement. 68 | 69 | 3. REQUIREMENTS 70 | 71 | A Contributor may choose to distribute the Program in object code form under 72 | its own license agreement, provided that: 73 | 74 | a) it complies with the terms and conditions of this Agreement; and 75 | b) its license agreement: 76 | i) effectively disclaims on behalf of all Contributors all warranties 77 | and conditions, express and implied, including warranties or 78 | conditions of title and non-infringement, and implied warranties or 79 | conditions of merchantability and fitness for a particular purpose; 80 | ii) effectively excludes on behalf of all Contributors all liability for 81 | damages, including direct, indirect, special, incidental and 82 | consequential damages, such as lost profits; 83 | iii) states that any provisions which differ from this Agreement are 84 | offered by that Contributor alone and not by any other party; and 85 | iv) states that source code for the Program is available from such 86 | Contributor, and informs licensees how to obtain it in a reasonable 87 | manner on or through a medium customarily used for software exchange. 88 | 89 | When the Program is made available in source code form: 90 | 91 | a) it must be made available under this Agreement; and 92 | b) a copy of this Agreement must be included with each copy of the Program. 93 | Contributors may not remove or alter any copyright notices contained 94 | within the Program. 95 | 96 | Each Contributor must identify itself as the originator of its Contribution, 97 | if 98 | any, in a manner that reasonably allows subsequent Recipients to identify the 99 | originator of the Contribution. 100 | 101 | 4. COMMERCIAL DISTRIBUTION 102 | 103 | Commercial distributors of software may accept certain responsibilities with 104 | respect to end users, business partners and the like. While this license is 105 | intended to facilitate the commercial use of the Program, the Contributor who 106 | includes the Program in a commercial product offering should do so in a manner 107 | which does not create potential liability for other Contributors. Therefore, 108 | if a Contributor includes the Program in a commercial product offering, such 109 | Contributor ("Commercial Contributor") hereby agrees to defend and indemnify 110 | every other Contributor ("Indemnified Contributor") against any losses, 111 | damages and costs (collectively "Losses") arising from claims, lawsuits and 112 | other legal actions brought by a third party against the Indemnified 113 | Contributor to the extent caused by the acts or omissions of such Commercial 114 | Contributor in connection with its distribution of the Program in a commercial 115 | product offering. The obligations in this section do not apply to any claims 116 | or Losses relating to any actual or alleged intellectual property 117 | infringement. In order to qualify, an Indemnified Contributor must: 118 | a) promptly notify the Commercial Contributor in writing of such claim, and 119 | b) allow the Commercial Contributor to control, and cooperate with the 120 | Commercial Contributor in, the defense and any related settlement 121 | negotiations. The Indemnified Contributor may participate in any such claim at 122 | its own expense. 123 | 124 | For example, a Contributor might include the Program in a commercial product 125 | offering, Product X. That Contributor is then a Commercial Contributor. If 126 | that Commercial Contributor then makes performance claims, or offers 127 | warranties related to Product X, those performance claims and warranties are 128 | such Commercial Contributor's responsibility alone. Under this section, the 129 | Commercial Contributor would have to defend claims against the other 130 | Contributors related to those performance claims and warranties, and if a 131 | court requires any other Contributor to pay any damages as a result, the 132 | Commercial Contributor must pay those damages. 133 | 134 | 5. NO WARRANTY 135 | 136 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED ON AN 137 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR 138 | IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, 139 | NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each 140 | Recipient is solely responsible for determining the appropriateness of using 141 | and distributing the Program and assumes all risks associated with its 142 | exercise of rights under this Agreement , including but not limited to the 143 | risks and costs of program errors, compliance with applicable laws, damage to 144 | or loss of data, programs or equipment, and unavailability or interruption of 145 | operations. 146 | 147 | 6. DISCLAIMER OF LIABILITY 148 | 149 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY 150 | CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, 151 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION 152 | LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 153 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 154 | ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE 155 | EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY 156 | OF SUCH DAMAGES. 157 | 158 | 7. GENERAL 159 | 160 | If any provision of this Agreement is invalid or unenforceable under 161 | applicable law, it shall not affect the validity or enforceability of the 162 | remainder of the terms of this Agreement, and without further action by the 163 | parties hereto, such provision shall be reformed to the minimum extent 164 | necessary to make such provision valid and enforceable. 165 | 166 | If Recipient institutes patent litigation against any entity (including a 167 | cross-claim or counterclaim in a lawsuit) alleging that the Program itself 168 | (excluding combinations of the Program with other software or hardware) 169 | infringes such Recipient's patent(s), then such Recipient's rights granted 170 | under Section 2(b) shall terminate as of the date such litigation is filed. 171 | 172 | All Recipient's rights under this Agreement shall terminate if it fails to 173 | comply with any of the material terms or conditions of this Agreement and does 174 | not cure such failure in a reasonable period of time after becoming aware of 175 | such noncompliance. If all Recipient's rights under this Agreement terminate, 176 | Recipient agrees to cease use and distribution of the Program as soon as 177 | reasonably practicable. However, Recipient's obligations under this Agreement 178 | and any licenses granted by Recipient relating to the Program shall continue 179 | and survive. 180 | 181 | Everyone is permitted to copy and distribute copies of this Agreement, but in 182 | order to avoid inconsistency the Agreement is copyrighted and may only be 183 | modified in the following manner. The Agreement Steward reserves the right to 184 | publish new versions (including revisions) of this Agreement from time to 185 | time. No one other than the Agreement Steward has the right to modify this 186 | Agreement. The Eclipse Foundation is the initial Agreement Steward. The 187 | Eclipse Foundation may assign the responsibility to serve as the Agreement 188 | Steward to a suitable separate entity. Each new version of the Agreement will 189 | be given a distinguishing version number. The Program (including 190 | Contributions) may always be distributed subject to the version of the 191 | Agreement under which it was received. In addition, after a new version of the 192 | Agreement is published, Contributor may elect to distribute the Program 193 | (including its Contributions) under the new version. Except as expressly 194 | stated in Sections 2(a) and 2(b) above, Recipient receives no rights or 195 | licenses to the intellectual property of any Contributor under this Agreement, 196 | whether expressly, by implication, estoppel or otherwise. All rights in the 197 | Program not expressly granted under this Agreement are reserved. 198 | 199 | This Agreement is governed by the laws of the State of New York and the 200 | intellectual property laws of the United States of America. No party to this 201 | Agreement will bring a legal action under this Agreement more than one year 202 | after the cause of action arose. Each party waives its rights to a jury trial in 203 | any resulting litigation. 204 | 205 | -------------------------------------------------------------------------------- /src/AppSecInc.ProcessDomain/Remoting/RemotingOptions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Net; 5 | using System.Net.Security; 6 | using System.Reflection; 7 | using System.Security.Principal; 8 | using System.Text; 9 | using AppSecInc.ProcessDomain.Remoting.Attributes; 10 | 11 | namespace AppSecInc.ProcessDomain.Remoting 12 | { 13 | // Source: http://msdn.microsoft.com/en-us/library/bb187421(v=vs.80).aspx 14 | /// 15 | /// Various options for configuring remoting between parent and child processes. The client settings will apply to the parent process creating the process domain and the server settings will apply 16 | /// to the process domain process itself. 17 | /// 18 | [Serializable] 19 | public sealed class RemotingOptions 20 | { 21 | public RemotingOptions() 22 | { 23 | Server = new ServerOptions(); 24 | Client = new ClientOptions(); 25 | SetPropertiesToDefaultValues(Server); 26 | SetPropertiesToDefaultValues(Client); 27 | } 28 | 29 | [Serializable] 30 | public class GeneralOptions 31 | { 32 | internal GeneralOptions() 33 | { 34 | ExtraProperties = new Hashtable(); 35 | } 36 | 37 | /// 38 | /// A Boolean value (true or false) that specifies whether communications on the channel are secure. The default is false. When it is set to true, the TokenImpersonationLevel property is set to Identification 39 | /// 40 | [PropertyName("secure")] 41 | [DefaultValue(false)] 42 | public bool Secure { get; set; } 43 | 44 | /// 45 | /// A value of type ProtectionLevel. The default is None, unless the secure property is set to true, in which case the default is EncryptAndSign. You must set the secure property to true to set the ProtectionLevel property to any value other than None. Note that None is the only setting that is compatible with Windows 95, Windows 98, or Windows Me. 46 | /// 47 | [PropertyName("protectionLevel")] 48 | [DefaultValue(ProtectionLevel.None)] 49 | public ProtectionLevel ProtectionLevel { get; set; } 50 | 51 | /// 52 | /// Any extra unimplemented properties to pass to IpcChannel 53 | /// 54 | public IDictionary ExtraProperties { get; private set; } 55 | } 56 | 57 | [Serializable] 58 | public sealed class ServerOptions : GeneralOptions 59 | { 60 | internal ServerOptions() 61 | { 62 | } 63 | 64 | /// 65 | /// A string that specifies the assembly, namespace, and class name of a class that implements the IAuthorizeRemotingConnection interface. The format of the string must be AuthorizationModuleNameSpace.AuthorizationModuleClass,AuthorizationModuleAssembly. 66 | /// 67 | [PropertyName("authorizationModule")] 68 | [DefaultValue(null)] 69 | public string AuthorizationModule { get; set; } 70 | 71 | /// 72 | /// A string that specifies the group or user that has permission to connect to this channel. The default is to allow access to all authorized users. 73 | /// 74 | [PropertyName("authorizedGroup")] 75 | [DefaultValue(null)] 76 | public string AuthorizedGroup { get; set; } 77 | 78 | /// 79 | /// A Boolean value (true or false) that specifies whether the server should impersonate the client. The default is false. 80 | /// 81 | [PropertyName("impersonate")] 82 | [DefaultValue(false)] 83 | public bool Impersonate { get; set; } 84 | } 85 | 86 | /// 87 | /// Remoting server options for the remote process domain process 88 | /// 89 | public ServerOptions Server { get; private set; } 90 | 91 | [Serializable] 92 | public sealed class ClientOptions : GeneralOptions 93 | { 94 | internal ClientOptions() 95 | { 96 | } 97 | 98 | /// 99 | /// A string that specifies the name that to be used as the connection group name on the server if the unsafeAuthenticatedConnectionSharing value is set to true. This property is ignored if unsafeAuthenticatedConnectionSharing is not set to true. If specified, make sure that this name maps to only one authenticated user. This property is supported only by version 1.1 or greater of the .NET Framework on the following platforms: Windows 98, Windows NT 4.0, Windows Me, Windows 2000, Windows XP Home Edition, Windows XP Professional, and the Windows Server 2003 family. 100 | /// 101 | [PropertyName("connectionGroupName")] 102 | [DefaultValue(null)] 103 | public string ConnectionGroupName { get; set; } 104 | 105 | /// 106 | /// An object that implements the ICredentials interface that represents the identity of the client. 107 | /// 108 | [PropertyName("credentials")] 109 | [DefaultValue(null)] 110 | public ICredentials Credentials { get; set; } 111 | 112 | /// 113 | /// A string that specifies a domain name to be used, in conjunction with the user name specified by username and the password specified by password, when authenticating to a server channel. 114 | /// 115 | [PropertyName("domain")] 116 | [DefaultValue(null)] 117 | public string Domain { get; set; } 118 | 119 | /// 120 | /// A string that specifies a password to be used, in conjunction with the user name specified by username and the domain specified by domain, when authenticating to a server channel. 121 | /// 122 | [PropertyName("password")] 123 | [DefaultValue(null)] 124 | public string Password { get; set; } 125 | 126 | 127 | /// 128 | /// A string that specifies the servicePrincipalName for Kerberos authentication. The default value is null. 129 | /// 130 | [PropertyName("serverPrincipalName")] 131 | [DefaultValue(null)] 132 | public string ServerPrincipalName { get; set; } 133 | 134 | /// 135 | /// A value of type TokenImpersonationLevel. This property specifies how the client is authenticated with the server. The default is None, unless the secure property is set to true, in which case the default is Identification. 136 | /// 137 | [PropertyName("tokenImpersonationLevel")] 138 | [DefaultValue(TokenImpersonationLevel.None)] 139 | public TokenImpersonationLevel TokenImpersonationLevel { get; set; } 140 | 141 | /// 142 | /// A Boolean value that indicates whether to allow high-speed NTLM-authenticated connection sharing. If this value is set to true, the connectionGroupName value must map to only one authenticated user. This property is ignored if the useAuthenticatedConnectionSharing value is set to true. This property is supported only by version 1.1 or greater of the .NET Framework on the following platforms: Windows 98, Windows NT 4.0, Windows Me, Windows 2000, Windows XP Home Edition, Windows XP Professional, and Windows Server 2003. 143 | /// 144 | [PropertyName("unsafeAuthenticatedConnectionSharing")] 145 | [DefaultValue(false)] 146 | public bool UnsafeAuthenticatedConnectionSharing { get; set; } 147 | 148 | /// 149 | /// A Boolean value that indicates whether the server channel reuses authenticated connections rather than authenticate each incoming call. By default, this value is set to true if the useDefaultCredentials value is also set to true; otherwise, the value is set to false, which means that each call is authenticated if the server requires authentication. This also applies to the programmatic equivalent, which is achieved either by creating an object that implements IDictionary, setting the credentials property to CredentialCache.DefaultCredentials, and passing that value to the channel sink, or by using the IDictionary returned from the ChannelServices.GetChannelSinkProperties method. This name/value pair is supported only by version 1.1 or greater of the .NET Framework on the following platforms: Microsoft Windows 98, Windows NT 4.0, Windows Millennium Edition (Windows Me), Windows 2000, Windows XP Home Edition, Windows XP Professional, and Windows Server 2003. 150 | /// 151 | [PropertyName("useAuthenticatedConnectionSharing")] 152 | [DefaultValue(true)] 153 | public bool UseAuthenticatedConnectionSharing { get; set; } 154 | 155 | /// 156 | /// A Boolean value that specifies whether to present credentials for the identity associated with the current thread when authenticating to a server channel. 157 | /// 158 | [PropertyName("useDefaultCredentials")] 159 | [DefaultValue(false)] 160 | public bool UseDefaultCredentials { get; set; } 161 | 162 | [PropertyName("username")] 163 | [DefaultValue(null)] 164 | public string Username { get; set; } 165 | } 166 | 167 | /// 168 | /// Remoting client options for the process creating and interacting with a process domain process 169 | /// 170 | public ClientOptions Client { get; private set; } 171 | 172 | internal void ApplyClientProperties(IDictionary properties) 173 | { 174 | Apply(Client, properties); 175 | } 176 | 177 | internal void ApplyServerProperties(IDictionary properties) 178 | { 179 | Apply(Server, properties); 180 | } 181 | 182 | private T GetAttribute(PropertyInfo propInfo) 183 | { 184 | var attribs = propInfo.GetCustomAttributes(typeof(T), false); 185 | if (attribs.Length == 0) 186 | return default(T); 187 | return (T)attribs[0]; 188 | } 189 | 190 | private void SetPropertiesToDefaultValues(GeneralOptions options) 191 | { 192 | if (options == null) 193 | throw new ArgumentNullException("options"); 194 | 195 | foreach (var propInfo in options.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public)) 196 | { 197 | var propNameAttrib = GetAttribute(propInfo); 198 | var defaultValueAttrib = GetAttribute(propInfo); 199 | if (propNameAttrib == null || defaultValueAttrib == null) 200 | continue; 201 | propInfo.SetValue(options, defaultValueAttrib.Value, null); 202 | } 203 | } 204 | 205 | private void Apply(GeneralOptions options, IDictionary properties) 206 | { 207 | if (options == null) 208 | throw new ArgumentNullException("options"); 209 | if (properties == null) 210 | throw new ArgumentNullException("properties"); 211 | 212 | foreach (var propInfo in options.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public)) 213 | { 214 | var propNameAttrib = GetAttribute(propInfo); 215 | var defaultValueAttrib = GetAttribute(propInfo); 216 | if (propNameAttrib == null || defaultValueAttrib == null) 217 | continue; 218 | var value = propInfo.GetValue(options, null); 219 | if ((value == null && defaultValueAttrib.Value == null) || (value != null && value.Equals(defaultValueAttrib.Value))) 220 | continue; 221 | properties[propNameAttrib.Name] = value; 222 | } 223 | 224 | if (options.ExtraProperties != null) 225 | { 226 | foreach (var key in options.ExtraProperties.Keys) 227 | { 228 | properties[key] = options.ExtraProperties[key]; 229 | } 230 | } 231 | } 232 | } 233 | } 234 | -------------------------------------------------------------------------------- /src/AppSecInc.ProcessDomain/Remoting/ActivatorProcess.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.IO; 4 | using System.Threading; 5 | using log4net; 6 | using AppSecInc.ProcessDomain.Utils; 7 | 8 | namespace AppSecInc.ProcessDomain.Remoting 9 | { 10 | public delegate void AttachedDelegate(); 11 | public delegate void DetachedDelegate(); 12 | 13 | /// 14 | /// Represents a process for a Process Domain and handles things such as attach/detach events and restarting the process 15 | /// 16 | internal class ActivatorProcess : IDisposable 17 | { 18 | static readonly ILog Logger = LogManager.GetLogger(typeof(ActivatorProcess)); 19 | public const string AssemblyName = "{0}.exe"; 20 | public const string ConfigName = "{0}.ctl"; 21 | 22 | public event AttachedDelegate Attached; 23 | public event DetachedDelegate Detached; 24 | 25 | readonly ProcessDomainSetup _setupInfo; 26 | readonly string _assemblyFile; 27 | readonly Process _process; 28 | readonly string _friendlyName; 29 | readonly string _setupInfoFile; 30 | ProcessStatus _processStatus; 31 | ActivatorClient _client; 32 | bool _disposed; 33 | 34 | private delegate void DeleteAssemblyFileDelegate(ManualResetEvent cancelEvent); 35 | 36 | public ActivatorProcess(string friendlyName, ProcessDomainSetup setupInfo) 37 | { 38 | Logger.InfoFormat("Creating ActivatorProcess for Process Domain '{0}' with the following configuration:", friendlyName); 39 | LogProcessDomainSetup(setupInfo); 40 | 41 | _friendlyName = friendlyName; 42 | _setupInfo = setupInfo; 43 | _assemblyFile = ActivatorHostAssemblyGenerator.CreateRemoteHostAssembly(friendlyName, setupInfo); 44 | Logger.InfoFormat("Generated Assembly: {0}", _assemblyFile); 45 | 46 | var startInfo = new ProcessStartInfo 47 | { 48 | FileName = _assemblyFile, 49 | CreateNoWindow = true, 50 | UseShellExecute = false, 51 | ErrorDialog = false, 52 | WorkingDirectory = _setupInfo.WorkingDirectory, 53 | }; 54 | 55 | if (_setupInfo.EnvironmentVariables != null) 56 | { 57 | foreach (var kv in _setupInfo.EnvironmentVariables) 58 | { 59 | startInfo.EnvironmentVariables[kv.Key] = kv.Value; 60 | } 61 | } 62 | 63 | _process = new Process 64 | { 65 | StartInfo = startInfo 66 | }; 67 | 68 | _setupInfoFile = Path.Combine(setupInfo.ExecutableDirectory, string.Format(ConfigName, friendlyName)); 69 | 70 | _process.Exited += Process_Exited; 71 | _process.EnableRaisingEvents = true; 72 | } 73 | 74 | private static void LogProcessDomainSetup(ProcessDomainSetup setupInfo) 75 | { 76 | Logger.InfoFormat("ExecutableDirectory = {0}", setupInfo.ExecutableDirectory); 77 | Logger.InfoFormat("ProcessStartTimeout = {0}", setupInfo.ProcessStartTimeout); 78 | Logger.InfoFormat("FileDeletionTimeout = {0}", setupInfo.FileDeletionTimeout); 79 | Logger.InfoFormat("DeleteOnUnload = {0}", setupInfo.DeleteOnUnload); 80 | Logger.InfoFormat("RestartOnProcessExit = {0}", setupInfo.RestartOnProcessExit); 81 | Logger.InfoFormat("WorkingDirectory = {0}", setupInfo.WorkingDirectory); 82 | } 83 | 84 | private void Process_Exited(object sender, EventArgs e) 85 | { 86 | LogExitReason(); 87 | 88 | var tmp = Detached; 89 | 90 | if (tmp != null) 91 | { 92 | tmp(); 93 | } 94 | 95 | // If Process Status is Active is because it was running and ended unexpectedly, 96 | // If status is Terminated then it was due to Terminate method invoked. 97 | if (_setupInfo.RestartOnProcessExit 98 | && _processStatus != ProcessStatus.Killed 99 | && _client != null) 100 | { 101 | Logger.InfoFormat("Restarting process for Process Domain '{0}'", _friendlyName); 102 | try 103 | { 104 | Start(); 105 | } 106 | catch (Exception ex) 107 | { 108 | Logger.Error(string.Format("Error restarting process for Process Domain '{0}'", _friendlyName), ex); 109 | } 110 | } 111 | } 112 | 113 | /// 114 | /// Logs the current action taken. 115 | /// 116 | private void LogExitReason() 117 | { 118 | switch (_processStatus) 119 | { 120 | case ProcessStatus.Active: 121 | Logger.WarnFormat("Process Domain '{0}' process exited unexpectedly", _friendlyName); 122 | break; 123 | case ProcessStatus.Killed: 124 | Logger.InfoFormat("Process Domain '{0}' process exited", _friendlyName); 125 | break; 126 | case ProcessStatus.Terminated: 127 | Logger.InfoFormat("Process Domain '{0}' process restarted", _friendlyName); 128 | break; 129 | } 130 | } 131 | 132 | /// 133 | /// Starts the remote process which will host an Activator 134 | /// 135 | public void Start() 136 | { 137 | CheckDisposed(); 138 | DisposeClient(); 139 | 140 | Logger.InfoFormat("Starting process for Process Domain '{0}'", _friendlyName); 141 | 142 | String processGuid = Guid.NewGuid().ToString(); 143 | 144 | bool created; 145 | var serverStartedHandle = new EventWaitHandle(false, EventResetMode.ManualReset, string.Format(ActivatorHost.EventName, processGuid), out created); 146 | 147 | // We set guid to a new value every time therefore this "should" never happen. 148 | if (!created) 149 | { 150 | throw new Exception("Event handle already existed for remote process"); 151 | } 152 | 153 | string processDomainAssemblyPath = AssemblyUtils.GetFilePathFromFileUri(typeof(ActivatorProcess).Assembly.CodeBase); 154 | ProcessDomainSetup.Serialize(_setupInfo, _setupInfoFile); 155 | 156 | // args[0] = process domain assembly path 157 | // args[1] = guid 158 | // args[2] = process id 159 | // args[3] = ProcessDomainSetup file 160 | _process.StartInfo.Arguments = string.Format("\"{0}\" {1} {2} \"{3}\"", processDomainAssemblyPath, processGuid, Process.GetCurrentProcess().Id, _setupInfoFile); 161 | 162 | if (!_process.Start()) 163 | { 164 | throw new Exception(string.Format("Failed to start process from: {0}", _process.StartInfo.FileName)); 165 | } 166 | 167 | Logger.InfoFormat("Process successfully started with process id {0}", _process.Id); 168 | 169 | if (!serverStartedHandle.WaitOne(_setupInfo.ProcessStartTimeout)) 170 | { 171 | throw new Exception("Timed-out waiting for remote process to start"); 172 | } 173 | 174 | serverStartedHandle.Close(); 175 | 176 | _processStatus = ProcessStatus.Active; 177 | _process.PriorityClass = _setupInfo.PriorityClass; 178 | _client = new ActivatorClient(processGuid, _setupInfo); 179 | 180 | var tmp = Attached; 181 | if (tmp != null) 182 | { 183 | tmp(); 184 | } 185 | } 186 | 187 | /// 188 | /// A proxy to the remote activator to use to create remote object instances 189 | /// 190 | public Activator Activator 191 | { 192 | get { return _client != null ? _client.Activator : null; } 193 | } 194 | 195 | /// 196 | /// Terminates this process, then it starts again. 197 | /// 198 | public void Terminate() 199 | { 200 | Logger.InfoFormat("Ternating Process Domain '{0}' process", _friendlyName); 201 | _processStatus = ProcessStatus.Terminated; 202 | 203 | try 204 | { 205 | if (!_process.HasExited) 206 | { 207 | _process.Kill(); 208 | } 209 | } 210 | catch (Exception ex) 211 | { 212 | Logger.Error("Failed to terminate Process Domain '{0}' process due to an exception", ex); 213 | throw; 214 | } 215 | } 216 | 217 | /// 218 | /// Kills the remote process. 219 | /// 220 | public void Kill() 221 | { 222 | Logger.InfoFormat("Killing Process Domain '{0}' process", _friendlyName); 223 | _processStatus = ProcessStatus.Killed; 224 | 225 | try 226 | { 227 | if (!_process.HasExited) 228 | { 229 | _process.Kill(); 230 | _process.WaitForExit(); 231 | } 232 | } 233 | catch (Exception ex) 234 | { 235 | Logger.Error("Failed to kill Process Domain '{0}' process due to an exception", ex); 236 | throw; 237 | } 238 | 239 | FreeResources(); 240 | } 241 | 242 | /// 243 | /// It deletes the assembly and the setup info file from the system. 244 | /// 245 | private void FreeResources() 246 | { 247 | if (_setupInfo.DeleteOnUnload) 248 | { 249 | DeleteAssemblyFileDelegate deleteAssemblyFileDelegate = DeleteAssemblyFile; 250 | 251 | var cancelEvent = new ManualResetEvent(false); 252 | var result = deleteAssemblyFileDelegate.BeginInvoke(cancelEvent, null, null); 253 | 254 | Logger.InfoFormat("Deleting file with timeout: {0}", _setupInfo.FileDeletionTimeout); 255 | if (!result.AsyncWaitHandle.WaitOne(_setupInfo.FileDeletionTimeout)) 256 | { 257 | //this will send a signal to cancel delete operation. 258 | cancelEvent.Set(); 259 | } 260 | 261 | // Free resourses and get the last exception thrown by delete file logic. 262 | // we need a loop here because operating system might not necessarily release file handle immediately 263 | // after stopping the process. 264 | try 265 | { 266 | deleteAssemblyFileDelegate.EndInvoke(result); 267 | } 268 | catch (Exception lastException) 269 | { 270 | Logger.Error(string.Format("Failed to delete Process Domain '{0}' assembly due to an exception", _friendlyName), lastException); 271 | throw new DeleteOnUnloadException(string.Format("Failed to delete Process Domain '{0}' assembly", _friendlyName), lastException); 272 | } 273 | 274 | try 275 | { 276 | File.Delete(_setupInfoFile); 277 | } 278 | catch (Exception lastException) 279 | { 280 | Logger.Error(string.Format("Failed to delete Process Domain '{0}' configuration file due to an exception", _friendlyName), lastException); 281 | throw new DeleteOnUnloadException(string.Format("Failed to delete Process Domain '{0}' configuration file", _friendlyName), lastException); 282 | } 283 | } 284 | } 285 | 286 | /// 287 | /// Deletes assembly file from the disk. 288 | /// This method will try until it succeeds or until stopped by an even. 289 | /// 290 | private void DeleteAssemblyFile(ManualResetEvent cancelEvent) 291 | { 292 | bool deleted = false; 293 | bool canceled; 294 | 295 | Exception lastException = null; 296 | 297 | do 298 | { 299 | try 300 | { 301 | File.Delete(_assemblyFile); 302 | if (_setupInfo.CopyConfigurationFile) 303 | { 304 | var configFile = _assemblyFile + ".config"; 305 | if (File.Exists(configFile)) 306 | File.Delete(configFile); 307 | } 308 | deleted = true; 309 | } 310 | catch (Exception ex) 311 | { 312 | //save last exception. 313 | lastException = ex; 314 | Thread.Sleep(100); 315 | } 316 | 317 | canceled = cancelEvent.WaitOne(0); 318 | 319 | } while (!deleted && !canceled); 320 | 321 | if (!deleted && lastException != null) 322 | { 323 | throw lastException; 324 | } 325 | } 326 | 327 | public void Dispose() 328 | { 329 | if (_disposed) 330 | return; 331 | _disposed = true; 332 | DisposeClient(); 333 | } 334 | 335 | private void CheckDisposed() 336 | { 337 | if (_disposed) 338 | { 339 | throw new ObjectDisposedException("ActivatorProcess"); 340 | } 341 | } 342 | 343 | private void DisposeClient() 344 | { 345 | if (_client != null) 346 | { 347 | _client.Dispose(); 348 | _client = null; 349 | } 350 | } 351 | } 352 | } -------------------------------------------------------------------------------- /src/AppSecInc.ProcessDomain.UnitTests/TestProcessDomain.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Runtime.Remoting; 6 | using System.Runtime.Serialization; 7 | using System.Runtime.Serialization.Formatters; 8 | using System.Security; 9 | using System.Security.Permissions; 10 | using System.Threading; 11 | using AppSecInc.ProcessDomain.Remoting; 12 | using AppSecInc.ProcessDomain.Utils; 13 | using NUnit.Framework; 14 | 15 | namespace AppSecInc.ProcessDomain.UnitTests 16 | { 17 | [TestFixture] 18 | public class TestProcessDomain 19 | { 20 | static readonly string TestObjectAssemblyName = typeof(RemoteTestObject).Assembly.FullName; 21 | static readonly string TestObjectTypeName = typeof(RemoteTestObject).FullName; 22 | 23 | static readonly ProcessDomainSetup DefaultSetupInfo = new ProcessDomainSetup 24 | { 25 | TypeFilterLevel = TypeFilterLevel.Full 26 | }; 27 | 28 | [Test] 29 | public void TestFriendlyName() 30 | { 31 | using (var domain = ProcessDomain.CreateDomain("ProcessDomain", DefaultSetupInfo)) 32 | { 33 | var obj = (RemoteTestObject)domain.CreateInstanceAndUnwrap(TestObjectAssemblyName, TestObjectTypeName); 34 | Assert.That(obj.GetProcessId(), Is.Not.EqualTo(Process.GetCurrentProcess().Id)); 35 | Assert.That(obj.GetProcessFileName(), Is.Not.EqualTo(Process.GetCurrentProcess().MainModule.FileName)); 36 | Assert.That(obj.GetProcessFileName().EndsWith("ProcessDomain.exe")); 37 | } 38 | } 39 | 40 | [Test] 41 | public void TestDomainAttachDetach() 42 | { 43 | var attachedEvent = new ManualResetEvent(false); 44 | var detachedEvent = new ManualResetEvent(false); 45 | 46 | using (var domain = ProcessDomain.CreateDomain("ProcessDomain", DefaultSetupInfo)) 47 | { 48 | domain.Attached += () => attachedEvent.Set(); 49 | domain.Detached += () => detachedEvent.Set(); 50 | 51 | var obj = (RemoteTestObject)domain.CreateInstanceAndUnwrap(TestObjectAssemblyName, TestObjectTypeName); 52 | Assert.That(!attachedEvent.WaitOne(0)); 53 | Assert.That(!detachedEvent.WaitOne(0)); 54 | 55 | // restart should occur, but our current object will be invalid 56 | Process.GetProcessById(obj.GetProcessId()).Kill(); 57 | Assert.That(detachedEvent.WaitOne(10000), "Timed-out waiting for process to die"); 58 | Assert.That(attachedEvent.WaitOne(10000), "Timed-out waiting for process to respawn"); 59 | Assert.Throws(() => obj.GetProcessId()); 60 | 61 | // create object in restarted domain 62 | obj = (RemoteTestObject)domain.CreateInstanceAndUnwrap(TestObjectAssemblyName, TestObjectTypeName); 63 | Assert.That(obj.GetProcessId(), Is.Not.EqualTo(Process.GetCurrentProcess().Id)); 64 | 65 | } 66 | 67 | var setupInfo = new ProcessDomainSetup 68 | { 69 | RestartOnProcessExit = false, 70 | TypeFilterLevel = TypeFilterLevel.Full 71 | }; 72 | 73 | // now restart should not occur 74 | using (var domain = ProcessDomain.CreateDomain("RemoteProcess2", setupInfo)) 75 | { 76 | domain.Attached += () => attachedEvent.Set(); 77 | domain.Detached += () => detachedEvent.Set(); 78 | 79 | var obj = (RemoteTestObject)domain.CreateInstanceAndUnwrap(TestObjectAssemblyName, TestObjectTypeName); 80 | Assert.That(obj.GetProcessId(), Is.Not.EqualTo(Process.GetCurrentProcess().Id)); 81 | 82 | attachedEvent.Reset(); 83 | detachedEvent.Reset(); 84 | 85 | Process.GetProcessById(obj.GetProcessId()).Kill(); 86 | 87 | Assert.That(detachedEvent.WaitOne(10000), "Timed-out waiting for process to die"); 88 | Assert.That(!attachedEvent.WaitOne(5000), "Unexpected re-attach"); 89 | Assert.Throws(() => obj.GetProcessId()); 90 | Assert.Throws(() => obj = (RemoteTestObject)domain.CreateInstanceAndUnwrap(TestObjectAssemblyName, TestObjectTypeName)); 91 | } 92 | } 93 | 94 | [Test] 95 | public void TestDefaultWorkingDirectory() 96 | { 97 | using (var domain = ProcessDomain.CreateDomain("ProcessDomain", DefaultSetupInfo)) 98 | { 99 | var obj = (RemoteTestObject)domain.CreateInstanceAndUnwrap(TestObjectAssemblyName, TestObjectTypeName); 100 | Assert.That(obj.CurrentDirectory, Is.EqualTo(Environment.CurrentDirectory)); 101 | } 102 | } 103 | 104 | [Test] 105 | public void TestExecutableLocation() 106 | { 107 | string desiredExecutableFileName = Path.Combine(Environment.CurrentDirectory, "MyDomain.exe"); 108 | 109 | var setupInfo = new ProcessDomainSetup 110 | { 111 | ExecutableDirectory = Environment.CurrentDirectory, 112 | TypeFilterLevel = TypeFilterLevel.Full 113 | }; 114 | 115 | // default uses temp directory 116 | using (var domain1 = ProcessDomain.CreateDomain("MyDomain", DefaultSetupInfo)) 117 | { 118 | var obj = (RemoteTestObject)domain1.CreateInstanceAndUnwrap(TestObjectAssemblyName, TestObjectTypeName); 119 | Assert.That(obj.GetProcessFileName(), Is.Not.EqualTo(desiredExecutableFileName)); 120 | } 121 | 122 | // now using our specified location 123 | using (var domain2 = ProcessDomain.CreateDomain("MyDomain", setupInfo)) 124 | { 125 | var obj = (RemoteTestObject)domain2.CreateInstanceAndUnwrap(TestObjectAssemblyName, TestObjectTypeName); 126 | Assert.That(obj.GetProcessFileName(), Is.EqualTo(desiredExecutableFileName)); 127 | } 128 | 129 | // test if file exists, it will be overwritten 130 | using (var writer = new StreamWriter(desiredExecutableFileName, false)) 131 | { 132 | writer.WriteLine("Garbage"); 133 | 134 | // will fail to compile because file is open 135 | Assert.Throws(() => ProcessDomain.CreateDomain("MyDomain", setupInfo)); 136 | 137 | writer.Flush(); 138 | } 139 | 140 | // file is now closed, but contains garbage that can't execute, 141 | // but the file will get overwritten 142 | using (var domain3 = ProcessDomain.CreateDomain("MyDomain", setupInfo)) 143 | { 144 | var obj = (RemoteTestObject)domain3.CreateInstanceAndUnwrap(TestObjectAssemblyName, TestObjectTypeName); 145 | Assert.That(obj.GetProcessFileName(), Is.EqualTo(desiredExecutableFileName)); 146 | } 147 | 148 | // and once we're done, file is gone 149 | Assert.That(!File.Exists(desiredExecutableFileName)); 150 | } 151 | 152 | [Test] 153 | public void TestDeleteFileOnExit() 154 | { 155 | string desiredExecutableFileName = Path.Combine(Environment.CurrentDirectory, "ProcessDomain.exe"); 156 | 157 | var setupInfo = new ProcessDomainSetup 158 | { 159 | ExecutableDirectory = Environment.CurrentDirectory, 160 | DeleteOnUnload = false 161 | }; 162 | 163 | ProcessDomain.Unload(ProcessDomain.CreateDomain("ProcessDomain", setupInfo)); 164 | Assert.That(File.Exists(desiredExecutableFileName)); 165 | 166 | setupInfo.DeleteOnUnload = true; 167 | 168 | ProcessDomain.Unload(ProcessDomain.CreateDomain("ProcessDomain", setupInfo)); 169 | Assert.That(!File.Exists(desiredExecutableFileName)); 170 | } 171 | 172 | [Test] 173 | public void TestConfigurationLocation() 174 | { 175 | // by default uses our app config 176 | using (var domain1 = ProcessDomain.CreateDomain("Domain1", DefaultSetupInfo)) 177 | { 178 | var obj = (RemoteTestObject)domain1.CreateInstanceAndUnwrap(TestObjectAssemblyName, TestObjectTypeName); 179 | Assert.That(obj.GetAppConfigValue("MyValue"), Is.EqualTo("MyValue")); 180 | } 181 | 182 | // now point it at a different app config 183 | var setupInfo = new ProcessDomainSetup 184 | { 185 | AppDomainSetupInformation = 186 | { 187 | ConfigurationFile = Path.Combine(Path.GetDirectoryName(AppDomain.CurrentDomain.SetupInformation.ConfigurationFile), "OtherApp.config"), 188 | }, 189 | TypeFilterLevel = TypeFilterLevel.Full 190 | }; 191 | 192 | using (var domain2 = ProcessDomain.CreateDomain("Domain2", setupInfo)) 193 | { 194 | var obj = (RemoteTestObject)domain2.CreateInstanceAndUnwrap(TestObjectAssemblyName, TestObjectTypeName); 195 | Assert.That(obj.GetAppConfigValue("MyValue"), Is.EqualTo("OtherValue")); 196 | } 197 | } 198 | 199 | [Test] 200 | public void TestProcessDomainAssemblyResolver() 201 | { 202 | string prevDirectory = Environment.CurrentDirectory; 203 | 204 | try 205 | { 206 | Directory.SetCurrentDirectory(Path.Combine(prevDirectory, "..")); 207 | 208 | using (var domain = ProcessDomain.CreateDomain("ProcessDomain", DefaultSetupInfo)) 209 | { 210 | // this used to fail because it would try to load the ProcessDomain assembly from 'current directory' 211 | } 212 | } 213 | finally 214 | { 215 | Directory.SetCurrentDirectory(prevDirectory); 216 | } 217 | } 218 | 219 | [Test] 220 | public void TestAssemblyUtils() 221 | { 222 | string uriPathUnescaped = "file:///c:/somepath/i have spaces"; 223 | string uriPathEscaped = "file:///c:/somepath/i%20have%20spaces"; 224 | string forwardSlashPath = "c:/somepath/i have spaces"; 225 | string path = @"c:\somepath\i have spaces"; 226 | 227 | Assert.That(AssemblyUtils.GetFilePathFromFileUri(path), Is.EqualTo(path)); 228 | Assert.That(AssemblyUtils.GetFilePathFromFileUri(forwardSlashPath), Is.EqualTo(path)); 229 | Assert.That(AssemblyUtils.GetFilePathFromFileUri(uriPathUnescaped), Is.EqualTo(path)); 230 | Assert.That(AssemblyUtils.GetFilePathFromFileUri(uriPathEscaped), Is.EqualTo(path)); 231 | } 232 | [Test] 233 | public void TestTypeFilterLevel() 234 | { 235 | // using an event handler requires type filter level Full for a remoting channel 236 | 237 | // by default, it's low, which will fail 238 | using (var domain = ProcessDomain.CreateDomain("Domain")) 239 | { 240 | Assert.Throws( 241 | () => { var obj = (RemoteTestObject)domain.CreateInstanceAndUnwrap(TestObjectAssemblyName, TestObjectTypeName); } 242 | ); 243 | } 244 | 245 | // now enable the remoting channel with type filter level Full 246 | var setup = new ProcessDomainSetup { TypeFilterLevel = TypeFilterLevel.Full }; 247 | using (var domain = ProcessDomain.CreateDomain("Domain", setup)) 248 | { 249 | var obj = (RemoteTestObject)domain.CreateInstanceAndUnwrap(TestObjectAssemblyName, TestObjectTypeName); 250 | 251 | obj.CalledBack = false; 252 | Assert.That(!obj.CalledBack); 253 | Assert.DoesNotThrow(() => obj.CallbackEvent += obj.SetCalledBack); 254 | obj.OnCallback(); 255 | Assert.That(obj.CalledBack); 256 | } 257 | } 258 | 259 | [Test] 260 | public void TestMultipleProcessDomains() 261 | { 262 | // Along with enabling support for TypeFilterLevel = Full, we also have to allow multiple 263 | // channels to be created. This will simply do just that and ensure there's no 264 | // duplicate channel registration exceptions 265 | using (var domain1 = ProcessDomain.CreateDomain("Domain1", DefaultSetupInfo)) 266 | using (var domain2 = ProcessDomain.CreateDomain("Domain2", DefaultSetupInfo)) 267 | { 268 | var obj1 = (RemoteTestObject)domain1.CreateInstanceAndUnwrap(TestObjectAssemblyName, TestObjectTypeName); 269 | var obj2 = (RemoteTestObject)domain2.CreateInstanceAndUnwrap(TestObjectAssemblyName, TestObjectTypeName); 270 | 271 | Assert.That(!obj1.CalledBack); 272 | Assert.That(!obj2.CalledBack); 273 | obj1.SetCalledBack(); 274 | obj2.SetCalledBack(); 275 | Assert.That(obj1.CalledBack); 276 | Assert.That(obj2.CalledBack); 277 | } 278 | } 279 | 280 | [Test] 281 | public void TestProcessPriority() 282 | { 283 | // Default case 284 | using (var domain = ProcessDomain.CreateDomain("TestPriorityDomain", DefaultSetupInfo)) 285 | { 286 | var obj = (RemoteTestObject)domain.CreateInstanceAndUnwrap(typeof(RemoteTestObject).Assembly.FullName, typeof(RemoteTestObject).FullName); 287 | Assert.That(obj.GetPriority(), Is.EqualTo(ProcessPriorityClass.Normal)); 288 | } 289 | 290 | // Try each priority 291 | foreach (ProcessPriorityClass priority in Enum.GetValues(typeof(ProcessPriorityClass))) 292 | { 293 | var setup = new ProcessDomainSetup { PriorityClass = priority, TypeFilterLevel = TypeFilterLevel.Full }; 294 | using (var domain = ProcessDomain.CreateDomain("TestPriorityDomain", setup)) 295 | { 296 | var obj = (RemoteTestObject)domain.CreateInstanceAndUnwrap(typeof(RemoteTestObject).Assembly.FullName, typeof(RemoteTestObject).FullName); 297 | 298 | // If not running as administrator, we can't run as RealTime, it will become High 299 | var expectedPriority = priority == ProcessPriorityClass.RealTime && !obj.RunningAsAdministrator() ? ProcessPriorityClass.High : priority; 300 | 301 | Assert.That(obj.GetPriority(), Is.EqualTo(expectedPriority)); 302 | } 303 | } 304 | } 305 | } 306 | } 307 | --------------------------------------------------------------------------------