PowerShell: Finding Currently Loaded DLLs

I was just browsing through the root\cimv2 WMI namespace this morning, using SAPIEN’s free WMI Explorer tool, when I happened across a WMI class called CIM_ProcessExecutable. In fact, what I was doing in a bit more detail, was going through the CIM_* classes, with the Instances tab selected, so I could discover if any of them were actually usable. I was stepping through them, in reverse order, by hitting {Up Arrow} + {Enter}, and finally came across one that had instances (this one, being CIM_ProcessExecutable).

Now, by clicking through a handful of these instances, it became quickly apparent to me that this class could be useful. It appeared that the classes’ purpose was to correlate running processes (instances of Win32_Process) with files on the filesystem (instances of CIM_DataFile), that are executable. Executable files would typically include files with the .dll or .exe file extensions.

Depending on what you are trying to accomplish, this could be incredibly useful information. For example, a lot of malware can load itself as a DLL in another process, or more simply, as its own executable. This info would be useful in discovering some (but not all) malware. Additionally, since you can get a reference to specific files on the filesystem, you could then determine the version of the file that is loaded into memory. This could be helpful if you are troubleshooting an issue that requires a specific version of an executable file.

Exploring the Class

If we simply enumerate a list of CIM_ProcessExecutable instances, we will get a long list of WMI paths to instances of other classes, specifically Win32_Process and CIM_DataFile. Go ahead and fire up you favorite PowerShell editor, or just the PowerShell console itself, and run this command:

Get-WmiObject -Namespace root\cimv2 -Class CIM_ProcessExecutable

Let’s find out how many executable files are currently loaded on the system. We do that simply by assigning the results of the above command to a PowerShell variable, and then echoing the value of its Count or Length property:

$ProcExes = Get-WmiObject -Namespace root\cimv2 -Class CIM_ProcessExecutable
Write-Host $ProcExes.Count

Retrieving File Properties

Let’s say that we want to just grab a list of interesting file properties, for the loaded executables. In order to do that, we can iterate over the items in $ProcExes, pass the WMI path for the CIM_DataFile instance to the WMI type accelerator in PowerShell, and then select a subset of the properties on the CIM_DataFile instance. Here’s how we do that:

$ProcExes = Get-WmiObject -Namespace root\cimv2 -Class CIM_ProcessExecutable

foreach ($item in $ProcExes)
    # Get the CIM_DataFile instance from the WMI path in Antecedent
    # Pass the CIM_DataFile object to the Select-Object cmdlet, and select only a few properties
    [wmi]"$($item.Antecedent)" | select FileName,Extension,Manufacturer,Version

Here is a sample of the output we’d get from running the above script:

Now we can take that even a step further. Let’s say our goal is to find out these same file properties, but only for non-Microsoft executables. This could help in a malware investigation, even a remote one, since we’re just using WMI here! We can use the Where-Object PowerShell cmdlet to filter the results that are written to the console. FYI, I am using the default short-hand for Where-Object, which is simply a question mark; Don’t let the syntax confuse you 🙂

foreach ($item in $ProcExes)
    # Get the CIM_DataFile instance from the WMI path in Antecedent
    # Filter for only files that are NOT from Microsoft
    # Pass the CIM_DataFile object to the Select-Object cmdlet, and select only a few properties
    [wmi]"$($item.Antecedent)" | ? { $_.Manufacturer -ne 'Microsoft Corporation' } | select FileName,Extension,Manufacturer,Version

As you can see, we’ve added the necessary code to filter out non-Microsoft files. That will work, at least as long as the file’s metadata is properly filled out. As you’ll see from the following screenshot, we still have some files that are obviously Microsoft PowerShell related, but were not caught because the Manufacturer field was missing from the assembly’s metadata.

Viewing Executables for a Process

Since the CIM_ProcessExecutable class binds executable files and processes together (these are not typically a 1:1 relationship), you might want to view all of the executable files that a particular process has loaded. The Dependent property of CIM_ProcessExecutable contains the WMI path to the process that has a particular executable file loaded. Each process has a unique ProcessID, but processes (instances of root\cimv2:Win32_Process) are uniquely identified in WMI by the Handle property. This is verified by examining the Win32_Process.Handle property in WMI Explorer (or wbemtest), and finding that the Key WMI qualifier is set to “True.” The discussion of WMI qualifiers is beyond the scope of this article, but in short, they are metadata that describe WMI properties. You can read more about WMI qualifiers on MSDN.

Typically, we won’t know the handle of a process, but we will likely know at least the name, or maybe even the process ID. Therefore, our process flow, for the goal we just mentioned, would look something like this:

  1. Input a process name
  2. Get the process Handle for the process(es) — we will actually just get the built-in __PATH property, because this directly correlates to the Dependent property on CIM_ProcessExecutable. Then we can avoid doing a LIKE query with just the process handle.
  3. Retrieve instances of CIM_DataFile for the process handle (this would be a string comparison on the Dependent property)
  4. For each instance of CIM_DataFile we retrieve: 1) get an instance of CIM_DataFile using the WMI path in Antecedent, 2) Echo the file properties to the console

Let’s take a look at the code necessary to make the above workflow happen:

[cc lang=”powershell”]
function Get-Executables
param (
[string] $ProcessName = $(throw “Please specify a process name.”)


Retrieve instance of Win32_Process based on the process name passed into the function

$Procs = Get-WmiObject -Namespace root\cimv2 -Query “select * from Win32_Process where Name = ‘$ProcessName'”

If there are no processes returned from the query, then simply exit the function

if (-not $Procs)
Write-Host -Object “No processes were found named $ProcessName”;

If one process is found, get the value of __PATH, which we will use for our next query

elseif (@($Procs).Count -eq 1)
Write-Verbose “One process was found named $ProcessName”;
$ProcPath = @($Procs)[0].__PATH;
Write-Verbose “Proc path is $ProcPath”;

If there is more than one process, use the process at index 0, for the time being

elseif ($Procs.Count -gt 1)
Write-Host -Object “More than one process was found named $ProcessName”;
$ProcPath = @($Procs)[0].__PATH;
Write-Host -Object “Using process with path: $ProcPath”;

Get the CIM_ProcessExecutable instances for the process we retrieved

$ProcQuery = “select * from CIM_ProcessExecutable where Dependent = ‘$ProcPath'”.Replace(‘\’,’\’);

Write-Verbose $ProcQuery
$ProcExes = Get-WmiObject -Namespace root\cimv2 -Query $ProcQuery;

If there are instances of CIM_ProcessExecutable for the specified process, go ahead and grab the important properties

if ($ProcExes)
foreach ($ProcExe in $ProcExes)

Use the [wmi] type accelerator to retrieve an instance of CIM_DataFile from the WMI __PATH in the Antecentdent property

$ExeFile = [wmi]”$($ProcExe.Antecedent)”

If the WMI instance we just retrieve “IS A” (think WMI operator) CIM_DataFile, then write properties to console

if ($ExeFile.__CLASS -eq ‘CIM_DataFile’)
Select-Object -InputObject $ExeFile -Property FileName,Extension,Manufacturer,Version -OutVariable $Executables

Do a little clean-up work

Write-Verbose “End: Cleaning up variables used for function”
Remove-Item -ErrorAction SilentlyContinue -Path variable:ExeFile,variable:ProcessName,variable:ProcExe,


Call the function we just defined, with its single parameter

Get-Executables -ProcessName excel.exe;
Here is an example of the output we would get, from running the above code. Make sure to change the process name, at the very bottom of the script, where we call the function, to something you’re interested in discovering information about.


That’s all I’ve got for now, folks! Hopefully this gives you some ideas around what else you can do with this information 🙂 There are lots of uncovered gems out there to help you with systems management, and this is just another one of them!

  • Trevor, this is just what I need for a project.

    I have an excel spreadsheet that calls a .NET object through COM interopt. I needed to debug the DLL, but it wasn’t loaded in excel.exe when I attached it to the process. Listdlls.exe revealed it was loaded in dllhost.exe. However , that’s not very scriptable. With this, I can look for a DLL, and then call a debugger and attach it to the project.

    • Justin,

      I’m glad that this worked for you! Your scenario sounds like a great use case for consuming the content that this WMI class offers!


  • obsolete

    What is the point of calling the replace method on line 36? When I run your script, I get the following error:

    Exception calling “Replace” with “2” argument(s): “String cannot be of zero length.
    Parameter name: oldValue”
    At line:1 char:1
    + $ProcPath.Replace(“”,””)
    + ~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo : NotSpecified: (:) [], MethodInvocationException
    + FullyQualifiedErrorId : ArgumentException

    My $ProcPath variable gets set to : \Blah-WS-INrootcimv2:Win32_Process.Handle=”8228″, and as far as I can tell, there seems to be some issue with the replace method, which then causes further issues if you make it down to $ProcQuery…

    • Hello, sorry about the issue. When I migrated my blog, it screwed up the backslashes. I fixed the final script in this article, so it should be good to go.

  • Anon Wibble

    I have a powershell script that can sometimes see things within a dll, sometimes cannot. I literally have no idea where to begin. There’s no other way to test this.

  • Remigio Oscar Iglesias

    You need to change this $ProcExes = Get-WmiObject -Namespace rootcimv2 -Class CIM_ProcessExecutable for this $ProcExes = Get-WmiObject -Namespace rootcimv2 -Class CIM_ProcessExecutable,, regards..