OnAssemblyLoad(
            object Sender,
            AssemblyLoadEventArgs Args
            )
        {
            //
            // STEP1: Wait for System.Management.Automation (SMA)
            //
            string assemblyName = Args.LoadedAssembly.GetName().Name;

            Console.WriteLine("[*] Loading assembly " + assemblyName);
            if (assemblyName != "System.Management.Automation")
            {
                return;
            }

            AppDomain.CurrentDomain.AssemblyLoad -= s_EventHandler;
            Assembly smaAssembly = Args.LoadedAssembly;

            //
            // You may want to break into a debugger for debugging.
            //
            //Debugger.Launch();

            //
            // STEP2: Determine a version of SMA
            //
            // Need a version of SMA since the ScanContent method exists only
            // in PowerShell v5 or later. PowerShell version can be obtained
            // via the PSVersion property of the PSVersionInfo class.
            //
            Type psVersionInfo = smaAssembly.GetType(
                "System.Management.Automation.PSVersionInfo");
            PropertyInfo psVersion = psVersionInfo.GetProperty(
                "PSVersion",
                BindingFlags.Static | BindingFlags.NonPublic,
                null,
                typeof(Version),
                Type.EmptyTypes,
                null);
            var version = (Version)psVersion.GetValue(null, null);

            if (version.Major != 5)
            {
                Console.WriteLine("[-] Unsupported PowerShell version detected.");
                return;
            }

            //
            // STEP3: Find methods via reflection
            //
            // We need tree methods per target method:
            //  target - A method to be hooked. It normally exists out side of
            //           our code.
            //  handler - A detour method to be called instead of the target
            //            method after a hook is installed.
            //  trampoline - A method used to call an original of the target
            //               method after a hook is installed.
            //
            const BindingFlags anyType = BindingFlags.Static |
                                         BindingFlags.Instance |
                                         BindingFlags.Public |
                                         BindingFlags.NonPublic;

            //
            // Indicates what parameters the methods take. Reflection requires
            // this information on the top of a method name since a method can
            // be overloaded for a different set of parameters.
            //
            // In our case, the methods are defined as follows:
            //  static AMSI_RESULT ScanContent(string Content,
            //                                 string SourceMetadata);
            //  static AMSI_RESULT ScanContentHookHandler(string Content,
            //                                            string SourceMetadata);
            //  static AMSI_RESULT ScanContentTrampoline(string Content,
            //                                           string SourceMetadata);
            //
            var targetMethodType     = new Type[] { typeof(string), typeof(string), };
            var handlerMethodType    = new Type[] { typeof(string), typeof(string), };
            var trampolineMethodType = new Type[] { typeof(string), typeof(string), };

            Type targetMethodClass = smaAssembly.GetType(
                "System.Management.Automation.AmsiUtils");
            Type handlerMethodClass    = typeof(HookScanContent);
            Type trampolineMethodClass = typeof(HookScanContent);

            MethodInfo target = targetMethodClass.GetMethod(
                "ScanContent",
                anyType,
                null,
                targetMethodType,
                null);
            MethodInfo hookHandler = handlerMethodClass.GetMethod(
                "ScanContentHookHandler",
                anyType,
                null,
                handlerMethodType,
                null);
            MethodInfo trampoline = trampolineMethodClass.GetMethod(
                "ScanContentTrampoline",
                anyType,
                null,
                trampolineMethodType,
                null);

            //
            // STEP4: Get addresses of native code of the methods
            //
            RuntimeHelpers.PrepareMethod(target.MethodHandle);
            RuntimeHelpers.PrepareMethod(hookHandler.MethodHandle);
            RuntimeHelpers.PrepareMethod(trampoline.MethodHandle);

            IntPtr targetAddr      = target.MethodHandle.GetFunctionPointer();
            IntPtr hookHandlerAddr = hookHandler.MethodHandle.GetFunctionPointer();
            IntPtr trampolineAddr  = trampoline.MethodHandle.GetFunctionPointer();

            //
            // STEP5: Install a hook on to the target method
            //
            // Overwrite native code of the ScanContent method. This is standard
            // inline hooking, only differences are that we initiate hooking
            // from C# (typically C/C++) and a target is compiled .NET native
            // code.
            //
            // This example code uses MinHook (https://github.com/TsudaKageyu/minhook)
            // for installing hooks since the author did not find any suitable
            // inline hooking library for C#.
            //
            if (!MinHook.InstallHook(targetAddr, hookHandlerAddr, trampolineAddr))
            {
                return;
            }

            //
            // STEP6: PROFIT!
            //
            Console.WriteLine("[*] The ScanContent method has been hooked.");
        }
        OnAssemblyLoad(
            object Sender,
            AssemblyLoadEventArgs Args
            )
        {
            //
            // STEP1: Wait for Microsoft.PowerShell.Commands.Utility (MPCU)
            //
            string assemblyName = Args.LoadedAssembly.GetName().Name;

            Console.WriteLine("[*] Loading assembly " + assemblyName);
            if (assemblyName != "Microsoft.PowerShell.Commands.Utility")
            {
                return;
            }

            AppDomain.CurrentDomain.AssemblyLoad -= s_EventHandler;
            Assembly mpcuAssembly = Args.LoadedAssembly;

            //
            // You may want to break into a debugger for debugging.
            //
            //Debugger.Launch();

            //
            // STEP2: Find methods via reflection
            //
            const BindingFlags anyType = BindingFlags.Static |
                                         BindingFlags.Instance |
                                         BindingFlags.Public |
                                         BindingFlags.NonPublic;

            var targetMethodType     = Type.EmptyTypes;
            var handlerMethodType    = new Type[] { typeof(HookProcessRecord), };
            var trampolineMethodType = Type.EmptyTypes;

            Type targetMethodClass = mpcuAssembly.GetType(
                "Microsoft.PowerShell.Commands.InvokeExpressionCommand");
            Type handlerMethodClass    = typeof(HookProcessRecord);
            Type trampolineMethodClass = typeof(HookProcessRecord);

            MethodInfo target = targetMethodClass.GetMethod(
                "ProcessRecord",
                anyType,
                null,
                targetMethodType,
                null);
            MethodInfo hookHandler = handlerMethodClass.GetMethod(
                "ProcessRecordHookHandler",
                anyType,
                null,
                handlerMethodType,
                null);
            MethodInfo trampoline = trampolineMethodClass.GetMethod(
                "ProcessRecordTrampoline",
                anyType,
                null,
                trampolineMethodType,
                null);

            //
            // STEP4: Get addresses of native code of the methods
            //
            RuntimeHelpers.PrepareMethod(target.MethodHandle);
            RuntimeHelpers.PrepareMethod(hookHandler.MethodHandle);
            RuntimeHelpers.PrepareMethod(trampoline.MethodHandle);

            IntPtr targetAddr      = target.MethodHandle.GetFunctionPointer();
            IntPtr hookHandlerAddr = hookHandler.MethodHandle.GetFunctionPointer();
            IntPtr trampolineAddr  = trampoline.MethodHandle.GetFunctionPointer();

            //
            // STEP5: Install a hook on to the target method
            //
            if (!MinHook.InstallHook(targetAddr, hookHandlerAddr, trampolineAddr))
            {
                return;
            }

            //
            // STEP6: PROFIT!
            //
            Console.WriteLine("[*] The ProcessRecord method has been hooked.");
        }