Porting Legacy Browser Helper and IE Extension Objects to .NET

Click here for larger image

Picture 1 - Peek at Document Object Model from Browser Helper Object

Environment: NET Framework Beta 2


This article describes the port of Browser Helper (BHO) and IE Extension objects, originally implemented in C++/ATL/WTL, to .NET class library implemented in C#. Some instructive problems and advantages of such implementation will be shown on concrete example.

Legacy ATL/WTL component code is available for comparison on this site at: http://www.codeguru.com/atl/AnalyzeIE.html

In addition, article will demonstrate dynamic TreeView form in C#, for which children nodes are created on demand. This is useful technique for large trees.

Implementing IObjectWithSite Interface

The legacy component is inproc COM server so I started by creating C# class library. C# class must be visible as a COM class and implement IObjectWithSite interface, through which we get other interfaces required to display Document Object Model (DOM) tree of a HTML document.

IObjectWithSite method with COM signature:

           SetSite( /* [in] */ IUnknown *pUnkSite);

is declared in the managed interface:

[ComImport, Guid("FC4801A3-2BA9-11CF-A229-00AA003D7352"),
public interface IObjectWithSite
  void SetSite( [In, MarshalAs(UnmanagedType.IUnknown)]
                       object pUnkSite);
  void GetSite( [In] ref Guid riid, [Out] IntPtr ppvSite);

MSDN Library specification for SetSite requires that incoming interface pointer is AddRef-ed before previously stored interface, if any, is released. However, .NET runtime will create so-called Runtime Callable Wrapper (RCW) to represent unmanaged COM interface and .NET object will be passed as input parameter. This may prompt you to assume that COM Interop code in .NET runtime will take care of all reference counting. Thus, if you defined member object to store incoming interface object like:

public class DOMPeek: IObjectWithSite
  private object m_IUnkSite;

method implementation should be as simple as:

void IObjectWithSite.SetSite( object pUnkSite)
  m_IUnkSite = pUnkSite; 

If you need more info about RCWs read MSDN article: http://msdn.microsoft.com/msdnmag/issues/01/08/Interop/Interop.asp

The implementation above will work fine but it is due to the way IE calls this method on BHO: when IE loads BHO it will call SetSite once with non-null interface parameter and, before it quits, it will call it second time with null parameter. Because IE is quitting it does not really matter if reference counts on objects that it used, and which are going away with it, are correct. However, if we want to make sure that IObjectWIthSite interface is implemented according to the specification, irrespective of its use in some specific situation, we can imagine some unmanaged client calling it like in the following code snippet:

IUnknown* pUnk = NULL;
// create some object and get its IUnknown
// at this point reference count on pUnk is 1

// now create our component that implements IObjectWithSite

IObjectWithSite* pSiteHolder = NULL;
hRes = CoCreateInstance( CLSID_AnalyzeIE, 

if ( SUCCEEDED( hRes))
  // pass pUnk as site pointer
  pSiteHolder->SetSite( pUnk); // this ads reference on 
                               // pUnk - should be 2

  // do something that does not change reference count of pUnk
  // .......

  pSiteHolder->SetSite( NULL);  // pUnk should be released by
                          // other object and its ref count 1

  // but if pUnk was not released above, let's 
  // release COM object and, presumably, also all 
  // corresponding .NET objects including RCWs.

  count = pSiteHolder->Release(); // this reference count 
                                  // is 0 - object goes away

// execute free unused libraries below and wait in
// debugger for about 10 minutes because COM Runtime
// does not really free all DLLs right after this call


count = pUnk->Release(); // should be 0, at least after
                         // few hours to make 100% sure 
                         // that COM freed all DLLS

When .NET runtime created RCW for pUnk parameter it did increment reference count to 2. However, in my tests, reference count on the last line above was 1, not 0 as expected. If runtime released RCW, it did not call Release on wrapped COM interface pointer.

While this is just one example, you can easily imagine situation where object behind pUnk would hold resources that you want freed. Then reference count of 1 would be a reason for concern.

If you read MSDN article referenced above, you may try to solve the reference count problem in this way:

void IObjectWithSite.SetSite( object pUnkSite)
  m_IUnkSite = pUnkSite; 
  GC.Collect(); // when pUnkSite is null GC will collect
                // old RCW and release interface?

However, in .NET Beta2 this won't work either. Implementation that will work is:

void IObjectWithSite.SetSite( object pUnkSite)
  if ( m_IUnkSite != null)
     Marshal.ReleaseComObject( m_IUnkSite);
  m_IUnkSite = pUnkSite; 

I don't know if final release of .NET will avoid the need to use ReleaseComObject. So be aware that COM interop issues may be more complex that you would expect and that .NET runtime is not always taking care of all reference counting.

Next comes the implementation of GetSite method with COM signature:

                       /*[iid_is][out]*/ void **ppvSite);

It required some experimentation to figure out how to deal with the outgoing void** parameter. A client calling this method on us can, in principle, pass any interface IID and we should return a pointer to that interface, if we did implement it. You will rarely explicitly query for interface in .NET managed languages. Instead, you cast a managed object representing some interface to an object representing requested interface. Interface querying is done 'behind the scenes' by the runtime. If query failed, you will get InvalidCast exception. But in this case we do not know which interface may be requested so we cannot use casts. Instead, we use Marshal.QueryInterface and, because it does not throw exception when query fails, we have to throw it ourselves.

void IObjectWithSite.GetSite( ref Guid riid, IntPtr ppvSite)
  const int e_fail = unchecked((int)0x80004005);

  if ( !ppvSite.Equals((IntPtr)0))
     IntPtr pvSite = (IntPtr)0;
     // be a good COM interface imp - NULL the
     // destination ptr first
     Marshal.WriteIntPtr( ppvSite, pvSite);

     if ( m_IUnkSite != null)
       IntPtr pUnk = 
             Marshal.GetIUnknownForObject( m_IUnkSite);
       Marshal.QueryInterface( pUnk, ref riid, out pvSite);
       Marshal.Release(pUnk); // GetIUnknownForObject 
                              // AddRefs so Release

       if ( !pvSite.Equals((IntPtr)0))
          Marshal.WriteIntPtr( ppvSite, pvSite);


Note that I defined standard COM E_FAIL HRESULT.

At this stage I wanted to make sure IE will see my .NET component as BHO COM component. That is, on top of the standard COM component registration that RegAsm tool does, BHO needs extra keys and values in the Registry so that IE can find it. These are added using RegistryKey class inside the method with the special attribute that makes it called during registration:

static void RegisterServer(String str1)

Sink for DWebBrowserEvents2

Once BHO is visible to IE, we can start adding code that will handle DWebBrowserEvents2 events. To make types from SHDocVw.dll (DWebBrowserEvents2 interface being one of them) available to BHO I added the reference to this DLL using "Add Reference" menu in VS.NET. Proxy DLL was created as the result.

One way to use this proxy DLL is described in MSDN Library article "Handling Events Raised by a COM Source" and accompanying sample code. However, that case is very different from ours because sample code has a client that actually creates managed IExplorer object. In our case unmanaged IExplorer already exists and our component was created by it as inproc server. We have no managed object on which to register our event handlers! In other words, we will have to implement DWebBrowserEvents2 interface.

I imagine some of you may now be thinking along these lines: implementing IObjectWithSite in legacy project was one line of code - we simply inherited ATL implementation. Here we had to struggle a bit, but the interface has only two methods. Now, however, we would need to implement DWebBrowserEvents2 and also hook up the sink via connection point interfaces! That certainly looks like a lot of work when compared to one line which caused all that to happen in ATL code.

Good news is that managed version of connection point hookup is easy and takes only few lines of code. What bothered me, however, was that I could not reuse more code from SHDocVW proxy DLL. I looked at it with ILDASM and found DWebBrowserEvents2_SinkHelper class that implements DWebBrowserEvents2, but this class doesn't have public constructor. Luckily, it does not prevent Activator class from creating instances:

SHDocVw.DWebBrowserEvents2_SinkHelper m_SinkHelper;
Type type = typeof(SHDocVw.DWebBrowserEvents2_SinkHelper);
m_SinkHelper =

You can see the rest in attached project sources. At this point browser events were arriving at the sink and the next step is GUI to show DOM tree.

DOM dialog with treeview

Somewhat non-standard GUI work here involved making the treeview resizable with the dialog. It takes very simple code in handlers for the Load and Resize dialog events. Much tougher, and ultimately unsolved, problem was how to make modeless dialog form owned by the IE window, as it is in legacy code. That is, displaying the dialog with:


makes it owned by the Desktop with the effect that, when you minimize browser window, it is still visible on screen. One Form can be owned by another by setting the Owner property. However, we do not have owner form but only browser window handle from IWebBrowser2's HWND property. I could not figure out, assuming it is doable, how to "wrap" a Form object around existing window handle. Best I could come up with was to make browser window parent of the dialog:

static extern int SetParent( int hWndChild, int hWndNewParent);

int parenthwnd = m_IWebBrowser2.HWND;
SetParent( m_DocDlg.Handle.ToInt32(), parenthwnd);

To get access to HTML DOM interfaces I added the reference to MSHTML.tlb. Beware - this can take several minutes (at one point I even thought that program locked up) and results in about 10 MB large proxy Interop.MSHTML_4_0.dll. It does seem like a lot to distribute along with our assembly, which takes only 36 KB. When .NET becomes more widespread, maybe the primary assemblies for popular legacy components will come preinstalled in Global Cache. Alternatively, one would hope that it would be possible to import only those types that one intends to use.

In ATL/WTL treeview we did not store text with each item but, instead, provided it in response to TVN_GETDISPINFO message. System.Windows.Forms.TreeView doesn't expose any event for this functionality. While data for display could not be provided on demand, I was still unwilling to construct potentially huge DOM tree each time user downloads HTML document. I have seen several, otherwise good, products where I had to wait about 15 seconds just to see a few children of the treeviev item on which I clicked. Software anticipated that I may want to look at all children's children, etc. and took its time to construct an entire branch. It is possible to do better! In C++ code we did set cChildren field of TVITEM structure, which in turn is the field in TV_INSERTSTRUCT, to tell treeview that item will have children, even if we didn't add any at that time. Nothing similar is available for treeview form in C#, so we must resort to the trick - we'll add a dummy child node so that we get BeforeExpand event when user clicks on parent node. At that point we remove the dummy and add children nodes. See source code for the details. Adding a context menu was somewhat simpler, in my judgment, than in C++ equivalent. Also, TreeView form has the method to expand an entire branch, while in C++ we had to code it explicitly and work around one Win32 treeview bug (or poorly documented feature.)

Tastes may differ but, comparing treeview-related code in ATL/WTL with C# one, I would say that this is where C# shines: for example, we use casts instead of COM smart pointers, there is no setting of various structure fields, altogether it presents less and more legible code.

IE Extension

The purpose of this object is to provide the user interface (button in IE toolbar and menu item on Tools menu) letting user stop dialog from popping up or building DOM tree each time some HTML page gets downloaded. It is the toggle that changes the value of the static variable:

public class DOMPeek: IDOMPeek, IObjectWithSite
  private static bool m_bShowDialog = true;

and, based on new value, closes or displays the dialog. If you create multiple instances of IExplorer (for example by clicking New Window menu item), there will be multiple instances of IE Extension object too. Even if this object is small, it makes sense to have only one instance of it. Note that I am not talking about C# language or .NET remoting singleton, but about controlling the way that COM factory creates new object instances. Unfortunately, I could not find anything in Beta 2 documentation to help me do this in C# code.

Extension needs to implement IOleCommandTarget interface and it also presented some problems. In particular, the structure passed to one of IOleCommandTarget mehods, defined in C header as:

typedef struct  _tagOLECMDTEXT
  DWORD cmdtextf;
  ULONG cwActual;
  ULONG cwBuf;
  /* [size_is] */ wchar_t rgwz[ 1 ];

could not be marshalled as managed structure

public struct OLECMDTEXT
  public uint cmdtextf; 
  public uint cwActual;
  public uint cwBuf; // specifies the number of chars in array
  public char[] rgwz; // string or whatever - nothing 
                      // works, no MarshalAs attributes help

and it looks like using IntPtr and manually poking it in unsafe code would be the only option, if we needed to read or change values of structure fields. IntPtr is .NET wrapper for native integer and I would recommend that, whenever you have problems marshalling a pointer parameter in COM, you try using IntPtr first. Namely, if you don't get managed type for parameter right, one of two things can happen:

  • interop layer will throw exception without ever calling your method. This can be confusing if you set the breakpoint in method entry, because you don't know if something else went wrong
  • method will be called but marshaled parameters will contain garbage. At this point you can start experimenting with parameter types and MarshalAs attribute

With IntPtr as parameter type you are more likely to encounter the second case. Going back to our problem, for our use of IE Extension OLECMDTEXT parameter can be ignored. So, let us keep in mind one more gotcha of the COM Interop (in Beta 2 at least) and proceed to the part where it does a nice job of making life easier, in comparison to the legacy code. First, look at the picture of a typical situation with multiple instances:

Click here for larger image

Picture 2 - BHO and IE Extension objects in two IE instances

Now, if you clicked Extension's toolbar button in instance 2, it has to call a method (blue pointed lines) on both instances of BHO. Should we worry about cross-apartment access then? One may think that we need not because BHO and IE Extension are C# classes. We can define static array for BHO instances like:

public class DOMPeek: IDOMPeek, IObjectWithSite
  public static ArrayList m_Instances = new ArrayList();

initialize array in SetSite:

void IObjectWithSite.SetSite( object pUnkSite)
  m_Instances.Add( this);

and provide static access method for IE Extension:

public static bool ToggleDialogShow()
  m_bShowDialog = !m_bShowDialog;
  // call code to close or display the  dialog
  return m_bShowDialog;

However, C# objects calling methods on other C# objects is not the only thing going on here: code that initializes dialog also queries COM interfaces and calls methods on those interface. For example:

private void SetTitle()
  MSHTML.IHTMLLocation ILoc = m_IDoc2.location;
  Text = ILoc.href;

which gets HTML page location and sets it as dialog's title. In effect, we are calling the method on COM interface pointer from different STA apartment. Morevover, the call above worked when executed inside the calling apartment but failed in other! Initially I took this as a sign that I do need to account for COM apartments and that some .NET equivalent of Global Interface Table (that helped cross apartments in C++) is required. However, other COM calls worked fine, prompting me to conclude that failure in getting location property is just a bug in interop proxy DLL. Another conclusion that we may draw is that RCWs wrap apartment-neutral COM interface pointers. Some posts on very useful DevelopMentor DOTNET discussion list indicate that this is indeed true or, in other words, .NET classes are "context agile" and multiple threads can execute within the same (in our case default) context.

The Final Hack

I encountered the following problem early in the development but I describe it last because solution is a hack. Namely, BHO can be loaded both by the Windows Explorer (WE) and IE. However, not only is this BHO intended only for IE but, if WE loads BHO, you won't be able to link, while you are developing, because linker can not overwrite the file in use. You must unregister the component, log out and log back in, to continue modifying and testing your code. Even worse - if there is a major bug in BHO that you just tested and you forgot to unregister it before logging out, the next time you log in Explorer may lock up immediately!

The hack starts with the observation that, when we register our assembly as a COM server using RegAsm, it is not the path to assembly that is stored under the InprocServer32 key. Instead, it points to the proxy, mscoree.dll, part of the .NET runtime. Therefore, we can insert another proxy DLL under the InprocServer32 key, with the difference that this one will allow only Internet Explorer to load it.

Consequently, I created the new DLL project in VS6, called wrapmscoree, with the entry code:

extern "C" BOOL WINAPI DllMain( HINSTANCE hInstance,
                                DWORD dwReason,
                                LPVOID /*lpReserved*/)
  if (dwReason == DLL_PROCESS_ATTACH)

  GetModuleFileName( NULL, Loader, MAX_PATH);
  for ( int i = lstrlen( Loader); i > 0; i--)
    if ( Loader[i] == _T('\\'))
       lstrcpy( Loader, Loader + i + 1);

  if ( lstrcmpi( Loader, _T("iexplore.exe")))
    return FALSE;
  if ( ( hLib = LoadLibrary(_T("mscoree.dll"))) == NULL)
    return FALSE;

As you can see, DllMain returns FALSE if it is not called by iexplore.exe and also loads the real .NET proxy. The only other functions in wrapmscoree.dll that IE, as COM client, cares about are implemented by calling the corresponding ones in mscoree.dll:

STDAPI DllCanUnloadNow(void)
  typedef HRESULT (_stdcall *fpCanUnloadNow)(void);
  fpCanUnloadNow fp;
  if ( hLib && ( fp = 
        (fpCanUnloadNow)GetProcAddress( hLib,
    return fp();
  return  S_OK;

STDAPI DllGetClassObject(REFCLSID rclsid, 
                         REFIID riid,
                         LPVOID* ppv)
  typedef HRESULT (_stdcall *fpGetClassObject)
                (REFCLSID rclsid, REFIID riid, LPVOID* ppv);
  fpGetClassObject fp;
  if ( hLib && ( fp = 
        (fpGetClassObject)GetProcAddress( hLib,
     return fp( rclsid, riid, ppv);
  return E_FAIL;

Finally, I added some code to change default InprocServer32 path set by RegAsm during registration.

Building and installing

I removed references to SHDocVw.dll and MSHTML.tlb from the solution file in attached project, because building the corresponding proxy assemblies can take a long time and they may not be located in \WINNT\System32 folder as they are on my machine. Therefore, you should add them yourself before building. BHO can be installed by copying it, together with support files, into any folder folowed by invoking RegAsm "path to BHO" /codebase on command prompt. Here is the list of the installed files:
  • AnalyzeIE.dll - BHO and IE Extension assembly
  • Interop.MSHTML_4_0.dll - MSHTML proxy
  • Interop.SHDocVw_1_1.dll - SHDocVw proxy
  • wrapmscoree.dll - DllInit hack
  • iespy.ico - IE Extension button standard icon
  • iespyhot.ico - IE Extension button hot icon


If DOM display is all that it does, there are no compelling reasons to re-implement Browser Helper Object and IE Extension in one of .NET managed languages. It is the "classic COM" component for use by the classic COM client. However, the amount of code to write is not large and it is not entirely trivial. Combined with the availability of original C++ code for comparison, I thought that the full port to C#, as opposed to one more tutorial, would be instructive.

You are more than welcome to comment on implementation decisions for which you think there are more elegant solutions in .NET framework. There certainly are issues that I haven't had the time to investigate in detail or simply don't understand as well as I would want to.

Few of such issues are listed below in no particular order:

  • can we tell COM factory to create COM singleton from managed code?
  • is there no simpler way to prevent loading of BHO by Windows Explorer except for the hack with wrapper DLL? Can the same be accomplished with managed code?
  • is there really no way to start with a window handle and attach it to Form object in managed code?
  • are standard COM HRESULTs defined somewhere in .NET and accessible to managed code?


Download demo project and source code - 22 Kb


  • Lightweight smart – Nike Let off TR Right in divulge 2013 3 series

    Posted by Tufffruntee on 04/22/2013 08:16am

    Nike Free TR Suited 3 prominent features is to use the additional forge: Nike Let off 5 soles improved bending Gouge; new tractor pattern making training more focused when; lighter weight, the permeability is stronger, and more in fashion shoe designs not only aim for shoes [url=http://markwarren.org.uk/goodbuy.cfm]nike free run[/url] more smug wearing, barefoot training sensible of, but also more in fashion appearance. Nike On the house TR Then 3 provides unequalled lateral solidity, you can be suffering with the legs in the part during training. Strong vamp upper breathable grating, demean foam's solitary lay out can be [url=http://northernroofing.co.uk/roofins.cfm]nike free[/url] seen from stem to stern it. Lightweight, demanding, reduce bubbles material habituated to by completely only one seams, more limber, forward is stronger. Requirement more mainstay, factor of a training utilize, lather close in more parts of the neediness championing agreeableness, effervescence loose. Put to use twice say nothing moisture wicking synthetic materials, vapid on your feet, mitigate maintain feet desiccated and comfortable. Phylite [url=http://northernroofing.co.uk/roofins.cfm]nike free[/url] midsole offers lightweight shock unchanging, superior durability and even outsole can do to greatly adjust the total avoirdupois of the shoe. Qianzhang pods on the outsole and heel-shaped Grassland rubber enhances the shoe multi-directional purchase on sundry surfaces.

  • You fancy some tomato basil and mozzarella. In behalf of indoor use, these slippers are as liven up and manueverable as sneakers.

    Posted by Soaceddew on 04/20/2013 01:00am

    Has honourable released some mod color Democratic Inneva Woven shoes, Nike recently with another pathway to discuss shoes with contrary styling to all [url=http://fossilsdirect.co.uk/glossarey.cfm]nike huarache[/url] eyes. This brings faithful edition Free Inneva Woven is a White Label of works in the series, represents shoes Italian made the assurance. Latest Allowed Inneva Woven jet and bawdy are on tap in two color schemes, to hand-knit Woven vamp in addition to infiltrated Italy's [url=http://northernroofing.co.uk/roofins.cfm]nike free uk[/url] finest crafts, for the moment gives athletes close to the foot of ease, the most consequential thing is the goal of Loose 5 configuration, barefoot be aware it resolution give birth to cannot be ignored. Nike Sovereign Inneva Woven SP Pale-complexioned Characterization Compact on Demonstration 16 at outlets about the [url=http://fossilsdirect.co.uk/glossarey.cfm]nike huarache free[/url] trade-mark on the shelves, and on trade in narrow tone, interested friends should settle terminate ralame to Nike announced the news.

  • New one

    Posted by snareenactina on 11/06/2012 07:48pm

    It costs about $5/day to feed someone, and less for them to occupy vacant housing/apartments/military bases. We can easily feed and house everyone. But we can't afford their tats, cel phones, nail jobs, rim jobs, etc. The government should seek to provide a very basic level of subsistence to those on the margins - enough to keep a person going, but only that. Part of the problem is that we're seeking to provide a lower middle class existence to the welfare class. Instead, we should make it easier for them to get by in the productive world by doing away with the laws and regulations that affect the cost of living, particularly rents and food. This evil government is actively discouraging independent food production. And the country is awash in surplus medical equipment - do we have to provide state of the art care to everyone with a pulse? If the welfare class received circa 2000 tech and meds, they'd still be ahead of 80% of the planet and 99% of all people who have ever lived. tering As unbelievable as it seems, Amy and I finally agree on something. Yes we have had presidents that refused to be transparent, we have one now. I do agree Amy that it does appear never work out, at least Obama certainly hasn't. arguing ayalew semiotic wigmore excited

  • Great!

    Posted by lbr_ on 12/18/2010 01:25am

    Thanks for the article.
    Your source code still works, I mean it is 2010 now ;))
    However I had to make few modifications - 
    in IObjectWithSite.SetSite
    m_SinkHelper = (SHDocVw.DWebBrowserEvents2_SinkHelper)Activator.CreateInstance(type, true);
    and in RegisterServer
    int lastSep = path.LastIndexOf( '\\');

  • How to prevent Windows Explorer to load the BHO

    Posted by bscholl on 08/23/2006 05:43am

    There is an easy way to prevent the BHO to be loaded by the Windows Explorer. Just add a dword value with name 'NoExplorer' to HKLM\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Browser Helper Objects\ and set it to 1. This will prevent WE to load the BHO

  • Great article, but can't get it working!

    Posted by Legacy on 04/11/2003 12:00am

    Originally posted by: Andy Alexander

    This is a great article and just what i've been looking for. I'm new to C# so appologise if the answer to these points is self evident.

    Firstly when i re-add the references to MSHTML and SHDocVw I get the message "'SHDocVw.DWebBrowserEvents2_SinkHelper' is inaccessible due to its protection level" when I try and build.

    Secondly, the maindlg.resx is giving me the error "Resource transformation for file 'maindlg.resx' failed. Invalid ResX input."

    Any help will be much appreaciated as building a BHO in C# has been top of my list of to-dos for a while.



  • About your questions.

    Posted by Legacy on 02/19/2002 12:00am

    Originally posted by: BHO

    Were you or anybody else able to answer the questions at the end of the article?

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Live Event Date: December 11, 2014 @ 1:00 p.m. ET / 10:00 a.m. PT Market pressures to move more quickly and develop innovative applications are forcing organizations to rethink how they develop and release applications. The combination of public clouds and physical back-end infrastructures are a means to get applications out faster. However, these hybrid solutions complicate DevOps adoption, with application delivery pipelines that span across complex hybrid cloud and non-cloud environments. Check out this …

  • With the average hard drive now averaging one terabyte in size, the fallout from the explosion of user-created data has become an overwhelming volume of potential evidence that law-enforcement and corporate investigators spend countless hours examining. Join Us and SANS' Rob Lee for our 45-minute webinar, A Triage and Collection Strategy for Time-Sensitive Investigations, will demonstrate how to: Identify the folders and files that often contain key insights Reduce the time spent sifting through content by …

Most Popular Programming Stories

More for Developers

RSS Feeds