Page 1 of 1

.NET Wrapper

Posted: Tue Sep 28, 2010 11:22 pm
by ddwyer
Hello all,
I have enjoyed using Sandboxie, and wished I had found it sooner. My usage is as an RDP replacement for running browser sessions. The US$50 it costs is simply amazing when you compare to the US$35k Microsoft wanted and US$20k Graphon GoGlobal costs to do much of our used functionality.

Anyway, here is source for a C# wrapper of the SBIE API, free to use, distribute, and modify as you see fit. I would prefer to know if you improve upon it (not hard to do), because I'll be posting this same code on CodeProject.com to sing the praises of Sandboxie soon.

Code: Select all

using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Text;

namespace SandboxieWrapper
{
    public class SandboxieNative
    {
        private static string ConvertFromWChar(byte[] incoming)
        {
            string outgoing = Encoding.Unicode.GetString(incoming);
            outgoing = outgoing.Remove(outgoing.IndexOf("\0"));
            return outgoing;
        }

        [DllImport("SbieDll.dll")]
        private static extern int SbieApi_EnumBoxes(
            int index,
            byte[] boxname);

        /// <summary>
        /// Retrieves the names of the sandboxes
        /// </summary>
        /// <returns>list of sandboxes as string</returns>
        public List<string> EnumerateSandboxes()
        {
            var sandboxes = new List<string>();
           
            int index = -1;
            while (true)
            {
                var name = new byte[34];
                index = SbieApi_EnumBoxes(index, name);
                if (index == -1) break;

                string sandboxname = ConvertFromWChar(name);
                sandboxes.Add(sandboxname);
            }
            return sandboxes;
        }

        public struct SandboxPaths
        {
            public string FilePath;
            public string KeyPath;
            public string IpcPath;
        }

        [DllImport("SbieDll.dll")]
        private static extern int SbieApi_QueryBoxPath(byte[] boxname,
                byte[] filepath,
                byte[] keypath,
                byte[] ipcpath,
                ref ulong filepathlen,
                ref ulong keypathlen,
                ref ulong ipcpathlen);

        /// <summary>
        /// Retrieves path information for a sandbox
        /// </summary>
        /// <param name="boxname">name of the sandbox</param>
        /// <returns>SandboxPaths struct</returns>
        public SandboxPaths QueryPaths(string boxname)
        {
            ulong fileLen = 0;
            ulong keyLen = 0;
            ulong ipcLen = 0;

            byte[] byteBoxName = Encoding.Unicode.GetBytes(boxname);
            SbieApi_QueryBoxPath(byteBoxName, null, null, null, ref fileLen, ref keyLen, ref ipcLen);
            
            var fileBuf = new byte[fileLen];
            var keyBuf = new byte[keyLen];
            var ipcBuf = new byte[ipcLen];


            SbieApi_QueryBoxPath(byteBoxName, fileBuf, keyBuf, ipcBuf, ref fileLen, ref keyLen, ref ipcLen);

            var paths = new SandboxPaths
                            {
                                FilePath = ConvertFromWChar(fileBuf),
                                KeyPath = ConvertFromWChar(keyBuf),
                                IpcPath = ConvertFromWChar(ipcBuf)
                            };

            return paths;
        }

        [DllImport("SbieDll.dll")]
        private static extern int SbieApi_QueryProcessPath(uint processid,
                byte[] filepath,
                byte[] keypath,
                byte[] ipcpath,
                ref ulong filepathlen,
                ref ulong keypathlen,
                ref ulong ipcpathlen);

        /// <summary>
        /// Retrieves path information for a sandbox
        /// </summary>
        /// <param name="processid">process id within a sandbox</param>
        /// <returns>SandboxPaths struct</returns>
        public SandboxPaths QueryPaths(uint processid)
        {
            ulong fileLen = 0;
            ulong keyLen = 0;
            ulong ipcLen = 0;

            SbieApi_QueryProcessPath(processid, null, null, null, ref fileLen, ref keyLen, ref ipcLen);

            var fileBuf = new byte[fileLen];
            var keyBuf = new byte[keyLen];
            var ipcBuf = new byte[ipcLen];


            SbieApi_QueryProcessPath(processid, fileBuf, keyBuf, ipcBuf, ref fileLen, ref keyLen, ref ipcLen);

            var paths = new SandboxPaths
            {
                FilePath = ConvertFromWChar(fileBuf),
                KeyPath = ConvertFromWChar(keyBuf),
                IpcPath = ConvertFromWChar(ipcBuf)
            };

            return paths;
        }

        [DllImport("SbieDll.dll")]
        private static extern int SbieApi_EnumProcessEx(
                byte[] boxname,
                bool allsessions,
                Int32 whichsession,
                [MarshalAs(UnmanagedType.LPArray)] 
                UInt32[] boxedpids);

        /// <summary>
        /// Retrieve a list of processes for a sandbox
        /// </summary>
        /// <param name="boxname">name of the sandbox</param>
        /// <param name="allsessions">flag indicating all sessions (true) or a specific session (false)</param>
        /// <param name="whichsession">if allessions is false, this indicates the specific session</param>
        /// <returns>list of process ids for the sandbox</returns>
        public List<UInt32> EnumerateProcesses(string boxname, bool allsessions, Int32 whichsession)
        {
            var pids = new UInt32[512];
            byte[] byteBoxName = Encoding.Unicode.GetBytes(boxname);
            SbieApi_EnumProcessEx(byteBoxName, allsessions, whichsession, pids);

            // first item contains count
            var pidlist = new List<UInt32>();
            for (int i = 1; i <= pids[0]; i++)
            {
                if (pids[i] > 0) pidlist.Add(pids[i]);
            }
            return pidlist;
        }

        /// <summary>
        /// Retrieves process information for all processes in a sandbox
        /// </summary>
        /// <param name="boxname">name of the sandbox</param>
        /// <returns>list of ProcessInformation struct</returns>
        public List<ProcessInformation> EnumerateProcesses(string boxname)
        {
            List<UInt32> pidlist = EnumerateProcesses(boxname,true,-1);
            var proclist = new List<ProcessInformation>();
            foreach (uint pid in pidlist)
            {
                proclist.Add(GetProcessInfo(pid));
            }
            return proclist;
        }

        [DllImport("SbieDll.dll")]
        private static extern int SbieApi_QueryProcess(
                uint processid,
                byte[] boxname,            // pointer to WCHAR [34]
                byte[] imagename,          // pointer to WCHAR [96]
                byte[] sidstring,          // pointer to WCHAR [96]
                UIntPtr sessionid);

        public struct ProcessInformation
        {
            public UInt32 ProcessId;
            public string BoxName;
            public string Imagename;
            public string SidString;
            public UInt32 SessionId;
        }

        /// <summary>
        /// retrieve information about a single process
        /// </summary>
        /// <param name="processid">process id to retrieve</param>
        /// <returns>ProcessInformation struct</returns>
        public ProcessInformation GetProcessInfo(uint processid)
        {
            var proc = new ProcessInformation();
            var boxname = new byte[34];
            var imagename = new byte[96];
            var sidstring = new byte[96];
            var sessionid = new UIntPtr();
            SbieApi_QueryProcess(processid, boxname, imagename, sidstring, sessionid);
            proc.ProcessId = processid;
            proc.BoxName = ConvertFromWChar(boxname);
            proc.Imagename = ConvertFromWChar(imagename);
            proc.SidString = ConvertFromWChar(sidstring);
            proc.SessionId = sessionid.ToUInt32();
            return proc;
        }

        [DllImport("SbieDll.dll")]
        private static extern bool SbieDll_KillOne(ulong processid);

        /// <summary>
        /// terminates a single process
        /// </summary>
        /// <param name="processid">process id to terminate</param>
        /// <returns>true on success</returns>
        public bool TerminateProcess(UInt32 processid)
        {
            return SbieDll_KillOne(processid);
        }

        [DllImport("SbieDll.dll")]
        private static extern bool SbieDll_KillAll(Int32 sessionid, byte[] boxname);

        /// <summary>
        /// terminates all processes in a sandbox
        /// </summary>
        /// <param name="boxname">name of the sandbox</param>
        /// <param name="sessionid">session id (default -1)</param>
        /// <returns>true on success</returns>
        public bool TerminateAllProcesses(string boxname, int sessionid=-1)
        {
            byte[] byteBoxName = Encoding.Unicode.GetBytes(boxname);
            return SbieDll_KillAll(sessionid, byteBoxName);
        }

        [DllImport("SbieDll.dll")]
        private static extern Int32 SbieApi_QueryConf(
            byte[] sectionname, // pointer to WCHAR [34]
            byte[] settingname, // pointer to WCHAR [66]
            UInt32 settingindex,
            byte[] value,
            UInt32 valuelen);

        /// <summary>
        /// Requests configuration information
        /// </summary>
        /// <param name="sectionName">section to retrieve</param>
        /// <param name="settingName">setting to retrieve</param>
        /// <param name="settingindex">if more than 1, the 0-based index to retrieve (default is zero)</param>
        /// <returns>value as string</returns>
        public string QueryConfiguration(string sectionName, string settingName, int settingindex=0)
        {
            byte[] byteSectionName = Encoding.Unicode.GetBytes(sectionName);
            byte[] byteSettingName = Encoding.Unicode.GetBytes(settingName);
            const uint valuelen = 8000;
            var valueBuf = new byte[valuelen];
            SbieApi_QueryConf(byteSectionName, byteSettingName, (UInt32)settingindex, valueBuf, valuelen);
            return ConvertFromWChar(valueBuf);
        }

        [DllImport("SbieDll.dll")]
        private static extern UInt32 SbieApi_ReloadConf(UInt32 sessionid);

        /// <summary>
        /// Causes the configuration file to be reloaded
        /// </summary>
        /// <param name="sessionid">session to reload it for or -1 for all</param>
        /// <returns>zero on success, a non-zero value on error</returns>
        public UInt32 ReloadConfiguration(int sessionid=-1)
        {
            return SbieApi_ReloadConf((UInt32)sessionid);
        }
       
        [DllImport("SbieDll.dll")]
        private static extern void SbieDll_Hook(char[] name, Delegate sourcefunc, Delegate detourfunc);

        /// <summary>
        /// UNTESTED METHOD to set a hook
        /// </summary>
        /// <param name="hookName">name of the hook to set</param>
        /// <param name="sourcefunction">delegate to override</param>
        /// <param name="detourfunction">delegate to detour toward</param>
        public void SetHook(string hookName, Delegate sourcefunction, Delegate detourfunction)
        {
            SbieDll_Hook(hookName.ToCharArray(), sourcefunction, detourfunction);
        }
        
    }
}

Posted: Wed Sep 29, 2010 3:47 am
by tzuk
That look very nice. But one minor point, is it possible for such .NET imports to not be tied to a specific DLL name?

.NET Wrapper

Posted: Wed Sep 29, 2010 9:50 am
by ddwyer
With static linking, no. With dynamic, you can, but it becomes a little more onerous to write the wrapper. All those attributes are annoying to specify in a variety of properties and methods.

Posted: Wed Sep 29, 2010 6:01 pm
by tzuk
I see. So keep in mind that in some scenarios, SbieDll is called something else. For instance, in 32-bit programs on 64-bit Windows, SbieDll is called SbieDllX.

Re: .NET Wrapper

Posted: Sat Oct 02, 2010 8:05 am
by The Martian
ddwyer wrote:Hello all,
I have enjoyed using Sandboxie, and wished I had found it sooner. My usage is as an RDP replacement for running browser sessions. The US$50 it costs is simply amazing when you compare to the US$35k Microsoft wanted and US$20k Graphon GoGlobal costs to do much of our used functionality.

Anyway, here is source for a C# wrapper of the SBIE API, free to use, distribute, and modify as you see fit. I would prefer to know if you improve upon it (not hard to do), because I'll be posting this same code on CodeProject.com to sing the praises of Sandboxie soon.
Hello Mr. ddwyer, much appreciate the code. A nice little starter pack for anyone (such as my-self) wishing to try their luck and Dev an app or two for use with Sandboxie.

Also, building on what you said "prefer to know if you improve upon it"; not sure of the chance of me improving on it as yet, but would definitely be interested in seeing any improvements or changes which you might be coming up with too. Thanks once again.

Update and Article Now Available

Posted: Wed Oct 27, 2010 7:07 pm
by ddwyer
Updated version and article posted to CodeProject
http://www.codeproject.com/KB/winsdk/Sa ... apper.aspx

Updated version on CodeProject Deleted

Posted: Thu Mar 15, 2012 9:30 am
by flip_lmb
Hi ddwyer, many thanks for your hard work! Unfortunately, the updated version on CodeProject was deleted on 1 Nov 2010. But not to worry, I've used your example and written a wrapper that loads Sandboxie dynamically (no more hard coded DllImport calls!). Hope this helps, and thanks to Sandboxie for a really useful tool!

Phil

Code: Select all

using System;
using System.IO;
using System.Linq;
using System.Text;
using System.Collections.Generic;
using System.Runtime.InteropServices;

namespace SandboxieWrapper
{
    public class SandboxPaths
    {
        public string FilePath;
        public string KeyPath;
        public string IpcPath;
    }

    public class ProcessInformation
    {
        public UInt32 ProcessId;
        public string BoxName;
        public string ImageName;
        public string SidString;
        public UInt32 SessionId;

        public override string ToString()
        {
            try { return string.IsNullOrWhiteSpace(this.ImageName) ? base.ToString() : this.ImageName; }
            catch { return base.ToString(); }
        }
    }

    /// <summary>
    /// Wrapper class to dynamically load Sandboxie dll (SbieDll.dll).  See http://www.sandboxie.com/index.php?SBIE_DLL_API
    /// </summary>
    public class Sandboxie : IDisposable
    {
        private const string kernal32dll = "kernel32.dll";

        [DllImport(kernal32dll)]
        private static extern IntPtr LoadLibrary(string dllToLoad);

        [DllImport(kernal32dll)]
        private static extern IntPtr GetProcAddress(IntPtr hModule, string procedureName);

        [DllImport(kernal32dll)]
        private static extern bool FreeLibrary(IntPtr hModule);

        /// <summary>
        /// Enumerate Sandbox Names
        /// </summary>
        /// <param name="index">Specifies which sandbox to return. Initialize to -1. Sandboxes are enumerated in the order they appear in Sandboxie.ini.</param>
        /// <param name="box_name">Receives the sandbox name</param>
        /// <returns>Returns the next value to use for the index parameter. Returns -1 when there is nothing left to enumerate.</returns>
        private delegate Int32 SbieApi_EnumBoxes(Int32 index, byte[] box_name);

        /// <summary>
        /// Query Sandbox Paths by Sandbox Name
        /// </summary>
        /// <param name="box_name">Specifies the name of the sandbox for which to return path information.</param>
        /// <param name="file_path">Receives the path to the root directory of the sandbox, as set by the FileRootPath setting. The buffer receives at most the number of bytes specified by the file_path_len parameter.  Pass NULL to ignore this parameter.</param>
        /// <param name="key_path">Receives the path to the root key of the sandbox registry, as set by the KeyRootPath setting. The buffer receives at most the number of bytes specified by the key_path_len parameter.  Pass NULL to ignore this parameter.</param>
        /// <param name="ipc_path">Receives the path to the root object directory of the sandbox, as set by the IpcRootPath setting. The buffer receives at most the number of bytes specified by the ipc_path_len parameter.  Pass NULL to ignore this parameter.</param>
        /// <param name="file_path_len">Specifies the length in bytes of the file_path buffer.  On return, receives the length in bytes needed to receive a complete buffer.</param>
        /// <param name="key_path_len">Specifies the length in bytes of the key_path buffer.  On return, receives the length in bytes needed to receive a complete buffer.</param>
        /// <param name="ipc_path_len">Specifies the length in bytes of the ipc_path buffer.  On return, receives the length in bytes needed to receive a complete buffer.</param>
        /// <returns>Returns zero on success, a non-zero value on error.</returns>
        private delegate Int32 SbieApi_QueryBoxPath(byte[] box_name, byte[] file_path, byte[] key_path, byte[] ipc_path,
            ref ulong file_path_len, ref ulong key_path_len, ref ulong ipc_path_len);

        /// <summary>
        /// Query Sandbox Paths by Process ID
        /// </summary>
        /// <param name="process_id">Specifies the ID of the sandboxed process to query.</param>
        /// <param name="file_path">Receives the path to the root directory of the sandbox, as set by the FileRootPath setting. The buffer receives at most the number of bytes specified by the file_path_len parameter.  Pass NULL to ignore this parameter.</param>
        /// <param name="key_path">Receives the path to the root key of the sandbox registry, as set by the KeyRootPath setting. The buffer receives at most the number of bytes specified by the key_path_len parameter.  Pass NULL to ignore this parameter.</param>
        /// <param name="ipc_path">Receives the path to the root object directory of the sandbox, as set by the IpcRootPath setting. The buffer receives at most the number of bytes specified by the ipc_path_len parameter.  Pass NULL to ignore this parameter.</param>
        /// <param name="file_path_len">Specifies the length in bytes of the file_path buffer.  On return, receives the length in bytes needed to receive a complete buffer.</param>
        /// <param name="key_path_len">Specifies the length in bytes of the key_path buffer.  On return, receives the length in bytes needed to receive a complete buffer.</param>
        /// <param name="ipc_path_len">Specifies the length in bytes of the ipc_path buffer.  On return, receives the length in bytes needed to receive a complete buffer.</param>
        /// <returns>Returns zero on success, a non-zero value on error.</returns>
        private delegate Int32 SbieApi_QueryProcessPath(uint process_id, byte[] file_path, byte[] key_path, byte[] ipc_path,
            ref ulong file_path_len, ref ulong key_path_len, ref ulong ipc_path_len);

        /// <summary>
        /// Enumerate Running Processes
        /// </summary>
        /// <param name="box_name">Specifies the name of the sandbox in which processes will be enumerated.</param>
        /// <param name="all_sessions">Specifies TRUE to enumerate processes in all logon sessions or only in a particular logon session</param>
        /// <param name="which_session">Specifies the logon session number in which processes will be enumerated.  Ignored if all_sessions if TRUE. Pass the value -1 to specify the current logon session.</param>
        /// <param name="boxed_pids">Receives the process ID (PID) numbers. The first ULONG receives the number of processes enumerated. The second ULONG receives the first PID, the third ULONG receives the second PID, and so on.</param>
        /// <returns>Returns zero on success, a non-zero value on error.</returns>
        private delegate Int32 SbieApi_EnumProcessEx(byte[] box_name, bool all_sessions, Int32 which_session,
            [MarshalAs(UnmanagedType.LPArray)] UInt32[] boxed_pids);

        /// <summary>
        /// Query Process Information
        /// </summary>
        /// <param name="process_id">Specifies the ID of the sandboxed process to query.</param>
        /// <param name="box_name">Receives the name of the sandbox in which the process is running.  Pass NULL to ignore this parameter.</param>
        /// <param name="image_name">Receives the process name.  Pass NULL to ignore this parameter.</param>
        /// <param name="sid_string">Receives the SID string for the process.  Pass NULL to ignore this parameter.</param>
        /// <param name="session_id">Receives the logon session number in which the process is running.  Pass NULL to ignore this parameter.</param>
        /// <returns>Returns zero on success, a non-zero value on error.</returns>
        private delegate Int32 SbieApi_QueryProcess(uint process_id, byte[] box_name, byte[] image_name, byte[] sid_string, UIntPtr session_id);

        /// <summary>
        /// Terminate a Single Sandboxed Process
        /// </summary>
        /// <param name="process_id">Specifies the process ID for the sandboxed process that should be terminated.</param>
        /// <returns>Returns TRUE on success, FALSE on failure.  The target process is terminated by the Sandboxie service (SbieSvc) with exit code 1 through a call to the Windows API TerminateProcess (ProcessId, 1).</returns>
        private delegate bool SbieDll_KillOne(uint process_id);

        /// <summary>
        /// Terminate All Sandboxed Processes
        /// </summary>
        /// <param name="session_id">Specifies the logon session number in which sandboxed programs should be terminated.  Specify -1 to indicate the current logon session.</param>
        /// <param name="box_name">Specifies the sandbox name in which sandboxed programs should be terminated.</param>
        /// <returns>Returns TRUE on success, FALSE on failure.  The target processes are terminated in the fashion described above; see SbieDll_KillOne.</returns>
        private delegate bool SbieDll_KillAll(Int32 session_id, byte[] box_name);

        /// <summary>
        /// Query Configuration from Sandboxie.ini
        /// </summary>
        /// <param name="section_name">Specifies the section name that contains the setting to query.</param>
        /// <param name="setting_name">Specifies the setting name to query.</param>
        /// <param name="setting_index">Specifies the zero-based index number for a setting that may appear multiple times.  The index number can be logically OR'ed with these special values: 0x40000000 - do not scan the [GlobalSettings] section if the specified setting name does appear in the specified section. 0x20000000 - do not expand any variables in the result.</param>
        /// <param name="value">Receives the value of the specified setting.</param>
        /// <param name="value_len">Specifies the maximum length in bytes of the buffer pointed to by the value parameter.</param>
        /// <returns>Returns zero on success.  Returns 0xC000008B if the setting was not found.  Any other return value indicates some other error.</returns>
        private delegate Int32 SbieApi_QueryConf(byte[] section_name, byte[] setting_name, UInt32 setting_index, byte[] value, UInt32 value_len);

        /// <summary>
        /// Reload Configuration from Sandboxie.ini
        /// </summary>
        /// <param name="session_id">Specifies the logon session number to which Sandboxie will log any error messages.  Pass -1 for the current logon session.</param>
        /// <returns></returns>
        private delegate Int32 SbieApi_ReloadConf(Int32 session_id);

        //TODO: SbieDll_Hook
        //TODO: DllCallback

        private bool disposed = false;
        private IntPtr dllPtr = IntPtr.Zero;

        public Sandboxie() : this(@"C:\Program Files\Sandboxie\SbieDll.dll")
        {
        }

        public Sandboxie(string sbieDllPath)
        {
            if (string.IsNullOrWhiteSpace(sbieDllPath))
                throw new ArgumentNullException("sbieDllPath");
            if (!File.Exists(sbieDllPath))
                throw new FileNotFoundException("Supplied SbieDll.dll not found", sbieDllPath);
            this.dllPtr = LoadLibrary(sbieDllPath);
            if (this.dllPtr == IntPtr.Zero)
                throw new FileLoadException("Unable to load supplied SbieDll.dll", sbieDllPath);
        }

        ~Sandboxie()
        {
            this.Dispose(false);
        }

        public virtual void Dispose()
        {
            this.Dispose(true);
            GC.SuppressFinalize(this);
        }

        /// <summary>
        /// Enumerate Sandbox Names
        /// </summary>
        /// <returns>An array of sandbox names</returns>
        public string[] EnumBoxes()
        {
            SbieApi_EnumBoxes extMethod = (SbieApi_EnumBoxes)this.GetExternalMethodDelegate<SbieApi_EnumBoxes>();

            List<string> result = new List<string>();
            Int32 index = -1;
            while (true)
            {
                byte[] sandboxNameBytes = new byte[34];
                index = extMethod(index, sandboxNameBytes);
                if (index == -1)
                    break;
                string sandboxName = ConvertFromWChar(sandboxNameBytes);
                result.Add(sandboxName);
            }
            return result.ToArray();
        }

        /// <summary>
        /// Query Sandbox Paths by Sandbox Name
        /// </summary>
        /// <param name="box_name">The name of the sandbox for which to return path information.</param>
        /// <returns>A <see cref="SandboxPaths"/> for the supplied <paramref name="box_name"/></returns>
        /// <exception cref="System.ArgumentNullException">Thrown when <paramref name="box_name"/> is null or empty</exception>
        /// <exception cref="System.ApplicationException">Thrown when <see cref="SbieApi_QueryBoxPath"/> returns non-zero</exception>
        public SandboxPaths QueryBoxPath(string box_name)
        {
            if (string.IsNullOrWhiteSpace(box_name))
                throw new ArgumentNullException("box_name");

            SbieApi_QueryBoxPath extMethod = (SbieApi_QueryBoxPath)this.GetExternalMethodDelegate<SbieApi_QueryBoxPath>();

            ulong file_path_len = 0;
            ulong key_path_len = 0;
            ulong ipc_path_len = 0;
            byte[] box_name_bytes = Encoding.Unicode.GetBytes(box_name);

            Int32 methodResult = extMethod(box_name_bytes, null, null, null, ref file_path_len, ref key_path_len, ref ipc_path_len);
            if (methodResult != 0)
                throw new ApplicationException(extMethod.GetType().Name + " returned " + methodResult.ToString());

            byte[] file_path = new byte[file_path_len];
            byte[] key_path = new byte[key_path_len];
            byte[] ipc_path = new byte[ipc_path_len];

            methodResult = extMethod(box_name_bytes, file_path, key_path, ipc_path, ref file_path_len, ref key_path_len, ref ipc_path_len);
            if (methodResult != 0)
                throw new ApplicationException(extMethod.GetType().Name + " returned " + methodResult.ToString());

            string filePath = ConvertFromWChar(file_path);
            string keyPath = ConvertFromWChar(key_path);
            string ipcPath = ConvertFromWChar(ipc_path);

            return new SandboxPaths() { FilePath = filePath, KeyPath = keyPath, IpcPath = ipcPath };
        }

        /// <summary>
        /// Query Sandbox Paths by Process ID
        /// </summary>
        /// <param name="process_id">Specifies the ID of the sandboxed process to query.</param>
        /// <returns>A <see cref="SandboxPaths"/> for the supplied <paramref name="process_id"/></returns>
        /// <exception cref="System.ApplicationException">Thrown when <see cref="SbieApi_QueryProcessPath"/> returns non-zero</exception>
        public SandboxPaths QueryProcessPath(uint process_id)
        {
            SbieApi_QueryProcessPath extMethod = (SbieApi_QueryProcessPath)this.GetExternalMethodDelegate<SbieApi_QueryProcessPath>();

            ulong file_path_len = 0;
            ulong key_path_len = 0;
            ulong ipc_path_len = 0;

            Int32 methodResult = extMethod(process_id, null, null, null, ref file_path_len, ref key_path_len, ref ipc_path_len);
            if (methodResult != 0)
                throw new ApplicationException(extMethod.GetType().Name + " returned " + methodResult.ToString());

            byte[] file_path = new byte[file_path_len];
            byte[] key_path = new byte[key_path_len];
            byte[] ipc_path = new byte[ipc_path_len];

            methodResult = extMethod(process_id, file_path, key_path, ipc_path, ref file_path_len, ref key_path_len, ref ipc_path_len);
            if (methodResult != 0)
                throw new ApplicationException(extMethod.GetType().Name + " returned " + methodResult.ToString());

            string filePath = ConvertFromWChar(file_path);
            string keyPath = ConvertFromWChar(key_path);
            string ipcPath = ConvertFromWChar(ipc_path);

            return new SandboxPaths() { FilePath = filePath, KeyPath = keyPath, IpcPath = ipcPath };
        }

        /// <summary>
        /// Enumerate Running Processes
        /// </summary>
        /// <param name="box_name">Specifies the name of the sandbox in which processes will be enumerated.</param>
        /// <param name="all_sessions">Specifies TRUE to enumerate processes in all logon sessions or only in a particular logon session</param>
        /// <param name="which_session">Specifies the logon session number in which processes will be enumerated.  Ignored if all_sessions if TRUE. Pass the value -1 to specify the current logon session.</param>
        /// <returns>An array of process ID's for the supplied <paramref name="box_name"/></returns>
        /// <exception cref="System.ArgumentNullException">Thrown when <paramref name="box_name"/> is null or empty</exception>
        public UInt32[] EnumProcess(string box_name, bool all_sessions = true, Int32 which_session = -1)
        {
            if (string.IsNullOrWhiteSpace(box_name))
                throw new ArgumentNullException("box_name");

            SbieApi_EnumProcessEx extMethod = (SbieApi_EnumProcessEx)this.GetExternalMethodDelegate<SbieApi_EnumProcessEx>();

            byte[] box_name_bytes = Encoding.Unicode.GetBytes(box_name);
            UInt32[] boxed_pids = new UInt32[512];

            Int32 methodResult = extMethod(box_name_bytes, all_sessions, which_session, boxed_pids);
            if (methodResult != 0)
                throw new ApplicationException(extMethod.GetType().Name + " returned " + methodResult.ToString());

            List<UInt32> result = new List<UInt32>();
            for (int loop = 1; loop <= boxed_pids[0]; loop++)
            {
                if (boxed_pids[0] > 0)
                    result.Add(boxed_pids[loop]);
            }
            return result.ToArray();
        }

        /// <summary>
        /// Query Process Information
        /// </summary>
        /// <param name="process_id">Specifies the ID of the sandboxed process to query.</param>
        /// <returns>A <see cref="ProcessInformation"/> for the supplied <paramref name="process_id"/></returns>
        /// <exception cref="System.ApplicationException">Thrown when <see cref="SbieApi_QueryProcess"/> returns non-zero</exception>
        public ProcessInformation QueryProcess(uint process_id)
        {
            SbieApi_QueryProcess extMethod = (SbieApi_QueryProcess)this.GetExternalMethodDelegate<SbieApi_QueryProcess>();

            byte[] box_name_bytes = new byte[34];
            byte[] image_name_bytes = new byte[96];
            byte[] sid_string_bytes = new byte[96];
            UIntPtr session_id = new UIntPtr();

            Int32 methodResult = extMethod(process_id, box_name_bytes, image_name_bytes, sid_string_bytes, session_id);
            if (methodResult != 0)
                throw new ApplicationException(extMethod.GetType().Name + " returned " + methodResult.ToString());

            string box_name = this.ConvertFromWChar(box_name_bytes);
            string image_name = this.ConvertFromWChar(image_name_bytes);
            string sid_string = this.ConvertFromWChar(sid_string_bytes);

            return new ProcessInformation()
            {
                ProcessId = process_id,
                BoxName = box_name,
                ImageName = image_name,
                SidString = sid_string,
                SessionId = session_id.ToUInt32()
            };
        }

        /// <summary>
        /// Terminate a Single Sandboxed Process
        /// </summary>
        /// <remarks>
        /// When run against version 3.58, fails with "SBIE2203 Failed to communicate with Sandboxie Service:  connect C0000041"
        /// See http://www.sandboxie.com/index.php?SBIE2203
        /// </remarks>
        /// <param name="process_id">Specifies the process ID for the sandboxed process that should be terminated.</param>
        /// <returns>Returns TRUE on success, FALSE on failure.  The target process is terminated by the Sandboxie service (SbieSvc) with exit code 1 through a call to the Windows API TerminateProcess (<paramref name="process_id"/>, 1).</returns>
        public bool KillOne(uint process_id)
        {
            SbieDll_KillOne extMethod = (SbieDll_KillOne)this.GetExternalMethodDelegate<SbieDll_KillOne>();
            return extMethod(process_id);
        }

        /// <summary>
        /// Terminate All Sandboxed Processes
        /// </summary>
        /// <param name="box_name">Specifies the sandbox name in which sandboxed programs should be terminated.</param>
        /// <returns>Returns TRUE on success, FALSE on failure.  The target processes are terminated in the fashion described above; see KillOne.</returns>
        /// <exception cref="System.ArgumentNullException">Thrown when <paramref name="box_name"/> is null or empty</exception>
        public bool KillAll(string box_name)
        {
            return this.KillAll(-1, box_name);
        }

        /// <summary>
        /// Terminate All Sandboxed Processes
        /// </summary>
        /// <param name="session_id">Specifies the logon session number in which sandboxed programs should be terminated.  Specify -1 to indicate the current logon session.</param>
        /// <param name="box_name">Specifies the sandbox name in which sandboxed programs should be terminated.</param>
        /// <returns>Returns TRUE on success, FALSE on failure.  The target processes are terminated in the fashion described above; see KillOne.</returns>
        /// <exception cref="System.ArgumentNullException">Thrown when <paramref name="box_name"/> is null or empty</exception>
        public bool KillAll(Int32 session_id, string box_name)
        {
            if (string.IsNullOrWhiteSpace(box_name))
                throw new ArgumentNullException("box_name");

            SbieDll_KillAll extMethod = (SbieDll_KillAll)this.GetExternalMethodDelegate<SbieDll_KillAll>();

            byte[] box_name_bytes = Encoding.Unicode.GetBytes(box_name);
            return extMethod(session_id, box_name_bytes);
        }

        /// <summary>
        /// Query Configuration from Sandboxie.ini
        /// </summary>
        /// <param name="section_name">Specifies the section name that contains the setting to query.</param>
        /// <param name="setting_name">Specifies the setting name to query.</param>
        /// <param name="setting_index">Specifies the zero-based index number for a setting that may appear multiple times.  The index number can be logically OR'ed with these special values: 0x40000000 - do not scan the [GlobalSettings] section if the specified setting name does appear in the specified section. 0x20000000 - do not expand any variables in the result.</param>
        /// <returns>Returns zero on success.  Returns 0xC000008B if the setting was not found.  Any other return value indicates some other error.</returns>
        /// <exception cref="System.ArgumentNullException">Thrown when <paramref name="section_name"/> is null or empty</exception>
        /// <exception cref="System.ArgumentNullException">Thrown when <paramref name="setting_name"/> is null or empty</exception>
        /// <exception cref="System.ApplicationException">Thrown when <see cref="SbieApi_QueryConf"/> returns non-zero</exception>
        public string QueryConf(string section_name, string setting_name, UInt32 setting_index = 0)
        {
            if (string.IsNullOrWhiteSpace(section_name))
                throw new ArgumentNullException("section_name");
            if (string.IsNullOrWhiteSpace(setting_name))
                throw new ArgumentNullException("setting_name");

            SbieApi_QueryConf extMethod = (SbieApi_QueryConf)this.GetExternalMethodDelegate<SbieApi_QueryConf>();

            byte[] section_name_bytes = Encoding.Unicode.GetBytes(section_name);
            byte[] setting_name_bytes = Encoding.Unicode.GetBytes(setting_name);
            byte[] value = new byte[8000];

            Int32 methodResult = extMethod(section_name_bytes, setting_name_bytes, setting_index, value, (UInt32)value.Length);
            if (methodResult != 0)
                throw new ApplicationException(extMethod.GetType().Name + " returned " + methodResult.ToString());

            return this.ConvertFromWChar(value);
        }

        /// <summary>
        /// Reload Configuration from Sandboxie.ini
        /// </summary>
        /// <param name="session_id">Specifies the logon session number to which Sandboxie will log any error messages.  Pass -1 for the current logon session.</param>
        public void ReloadConf(Int32 session_id = -1)
        {
            SbieApi_ReloadConf extMethod = (SbieApi_ReloadConf)this.GetExternalMethodDelegate<SbieApi_ReloadConf>();

            Int32 methodResult = extMethod(session_id);
            if (methodResult != 0)
                throw new ApplicationException(extMethod.GetType().Name + " returned " + methodResult.ToString());
        }

        /// <summary>
        /// Enumerate Running Query Process Information
        /// </summary>
        /// <param name="box_name">Specifies the name of the sandbox in which processes will be enumerated.</param>
        /// <param name="all_sessions">Specifies TRUE to enumerate processes in all logon sessions or only in a particular logon session</param>
        /// <param name="which_session">Specifies the logon session number in which processes will be enumerated.  Ignored if all_sessions if TRUE. Pass the value -1 to specify the current logon session.</param>
        /// <returns>An array of <see cref="ProcessInformation"/> for the supplied <paramref name="box_name"/></returns>
        /// <exception cref="System.ArgumentNullException">Thrown when <paramref name="box_name"/> is null or empty</exception>
        public ProcessInformation[] QueryEnumProcess(string box_name, bool all_sessions = true, Int32 which_session = -1)
        {
            if (string.IsNullOrWhiteSpace(box_name))
                throw new ArgumentNullException("box_name");

            UInt32[] pids = this.EnumProcess(box_name, all_sessions, which_session);
            return pids.Select(pid => this.QueryProcess(pid)).ToArray();
        }

        /// <summary>
        /// Returns true if the sandbox is idle (has no running processes)
        /// </summary>
        /// <param name="box_name">Specifies the name of the sandbox in which to check for running processes.</param>
        /// <returns>Returns true if the sandbox specified in <paramref name="box_name"/> has no running processes.</returns>
        /// <exception cref="System.ArgumentNullException">Thrown when <paramref name="box_name"/> is null or empty</exception>
        public bool BoxIsIdle(string box_name)
        {
            if (string.IsNullOrWhiteSpace(box_name))
                throw new ArgumentNullException("box_name");
            return this.EnumProcess(box_name).Length == 0;
        }

        /// <summary>
        /// Returns true if the sandbox is idle (has running processes)
        /// </summary>
        /// <param name="box_name">Specifies the name of the sandbox in which to check for running processes.</param>
        /// <returns>Returns true if the sandbox specified in <paramref name="box_name"/> has running processes.</returns>
        public bool BoxIsBusy(string box_name)
        {
            return !this.BoxIsIdle(box_name);
        }

        /// <summary>
        /// Enumerate Idle Sandbox Names
        /// </summary>
        /// <returns>An array of sandbox names that are idle</returns>
        public string[] EnumIdleBoxes()
        {
            return this.EnumBoxes().Where(box => this.BoxIsIdle(box)).ToArray();
        }

        private void Dispose(bool disposing)
        {
            if (!this.disposed)
            {
                try
                {
                    if (disposing)
                    {
                        if (this.dllPtr != IntPtr.Zero)
                            FreeLibrary(this.dllPtr);
                        this.dllPtr = IntPtr.Zero;
                    }
                }
                finally { this.disposed = true; }
            }
        }

        private Delegate GetExternalMethodDelegate<T>()
        {
            Type type = typeof(T);
            IntPtr funcAddr = GetProcAddress(this.dllPtr, type.Name);
            if (funcAddr == IntPtr.Zero)
                throw new ApplicationException(string.Format("External method '{0}' not found", type.Name));
            return Marshal.GetDelegateForFunctionPointer(funcAddr, type);
        }

        private string ConvertFromWChar(byte[] stringBytes)
        {
            string result = Encoding.Unicode.GetString(stringBytes);
            int idx = result.IndexOf("\0");
            if (idx != -1)
                result = result.Remove(idx);
            return result;
        }
    }
}