Use dotnet-dump command line tool to capture process dumps

Published on Thursday, May 20, 2021

Occasionally I have to dig through process dumps to find some nasty difficult to sort out bug. In the past I used the Task manager and right click to create a dump file

Right click to create dump file :Right click in Task manager Details tab to create dump file

which works fine, but sometimes that turns into several process dumps over time and from different processes at the same time. This repetitive work takes minutes of my day, so why not spend hours finding an automated solution!

Enter dotnet-dump

Using dotnet-dump it's easy peasy to create dump files from .NET Core 3.x and .NET 5+, and it's the same tool for whatever platform you're on.

Installing

You need to have have .NET whatever version SDK installed to install the tool, so if you haven't done that yet go download and install. Once that is in place you can install dotnet-dump. It's installed as a global command line tool by running the following in command line

dotnet tool install --global dotnet-dump

It's a different download for x64, x86 and arm etc. so pick the one that matches your application.

Dumping

After that you can create a dump of a process by running

dotnet-dump collect --process-id 123

That seems easy enough it wasn't for the process id. The dotnet-dump comes with a ps command for that:

PS C:\Windows\system32> dotnet-dump ps
       972 dotnet     C:\Program Files\dotnet\dotnet.exe
      1856 dotnet     C:\Program Files\dotnet\dotnet.exe
      3044 dotnet     C:\Program Files\dotnet\dotnet.exe
      3264 dotnet     C:\Program Files\dotnet\dotnet.exe
      3384 w3wp       C:\Windows\SysWOW64\inetsrv\w3wp.exe
      5440 w3wp       C:\Windows\SysWOW64\inetsrv\w3wp.exe
      5592 w3wp       C:\Windows\SysWOW64\inetsrv\w3wp.exe
      6088 w3wp       C:\Windows\SysWOW64\inetsrv\w3wp.exe
      9628 w3wp       c:\windows\system32\inetsrv\w3wp.exe

Which is good for listing what processes the tool supports, but I need a bit more information. The processes I'm dumping are on a server with several different ASP.NET Core applications running and as you can see there's four different dotnet-processes runnning (the w3wp are IIS worker processes and I don't need those).

The Search for a one-liner PowerShell call

Getting the process the PowerShell way

To get more information about a process I can use PowerShell's Get-Process

PS C:\Windows\system32> Get-Process

Handles  NPM(K)    PM(K)      WS(K)     CPU(s)     Id  SI ProcessName
-------  ------    -----      -----     ------     --  -- -----------
   3382     139    55652     114568   1 011,17   2084   0 ccSvcHst
    538      32   209860     200304   1 156,50   2116   0 ccSvcHst
    481      36     7160       5136       1,50   4072   2 ccSvcHst
    475      35     6664       5708       0,30   8800   7 ccSvcHst
    484      36     6852       3948       0,52  10152   6 ccSvcHst
     88       7     5260       9380       0,03   1408   0 conhost

That returns every process so it's quite a lengthy list, but as a start Get-Process can filter by name

PS C:\Windows\system32> Get-Process -Name dotnet

Handles  NPM(K)    PM(K)      WS(K)     CPU(s)     Id  SI ProcessName
-------  ------    -----      -----     ------     --  -- -----------
    626     135   100312     139240      34,88    972   0 dotnet
    606     137   100612     140280      37,98   1856   0 dotnet
    558      91    66268     100420      11,33   3044   0 dotnet
    546      91    69588     104200      10,42   3264   0 dotnet
    737     102   106256     140772      12,58   6800   0 dotnet

That's better, but it's still missing the application pool information. Luckily for me the application pools are run under the default user names so if I also add to output the name I can see those

PS C:\Windows\system32> Get-Process -Name dotnet -IncludeUserName

Handles      WS(K)   CPU(s)     Id UserName               ProcessName
-------      -----   ------     -- --------               -----------
    632     139224    34,89    972 IIS APPPOOL\Datema ... dotnet
    620     140312    38,06   1856 IIS APPPOOL\Datema ESC dotnet
    555     100436    11,39   3044 IIS APPPOOL\Datema ... dotnet
    538     104144    10,53   3264 IIS APPPOOL\Datema ESC dotnet
    740     140760    12,58   6800 IIS APPPOOL\Datema ... dotnet

There they are, but some of the longer ones are cropped "IIS APPPOOL\Datema ...". Well there's some more PowerShell magic that can format the returned table

PS C:\Windows\system32> Get-Process -Name dotnet -IncludeUserName | Format-Table -Wrap -AutoSize

Handles  WS(K) CPU(s)    Id UserName                      ProcessName
-------  ----- ------    -- --------                      -----------
    424  77572   2,58  3268 IIS APPPOOL\Datema ESC        dotnet
    595 123852  12,42  3572 IIS APPPOOL\Datema ES License dotnet
    460  72296   2,09  3820 IIS APPPOOL\Datema ESC 2.0    dotnet
    659 127604   8,69 10432 IIS APPPOOL\Datema ESC        dotnet

First of all the | character is called a pipe character. It takes the result of the previous command (Get-Process) and moves it into the next command (Format-Table). PowerShell is a object based language so it's actually passing an object that contains rows and properties and those properties can be accessed individually, which we use when it's time to filter the output. To filter the output from Get-Process we can pipe it to Where-object

PS C:\Windows\system32> Get-Process -Name dotnet -IncludeUserName | Where-Object UserName -like 'IIS APPPOOL\Datema ESC 2.0' | Format-Table -Wrap -AutoSize

Handles  WS(K) CPU(s)   Id UserName                   ProcessName
-------  ----- ------   -- --------                   -----------
    662 128160   8,77 7540 IIS APPPOOL\Datema ESC 2.0 dotnet

I first I tried to use -eq for comparison and that worked for for 'IIS APPPOOL\Datema ESC' but not for 'IIS APPPool ESC 2.0'. As far as I can guess it's just not exactly the same for some reason, but luckily there's -like which works fine. It's also important to know that the Format-Table -Wrap -AutoSize needs to be last or the result is empty.

So connecting that to dotnet-dump required a lot of trial and error but this is the result

PS C:\Windows\system32> Get-Process -Name dotnet -IncludeUserName | Where-Object UserName -like 'IIS APPPOOL\Datema ESC' | ForEach { & "dotnet-dump" @("collect", "--process-id", $_.Id) }

Writing full to C:\Windows\system32\dump_20210520_121145.dmp
Complete

Writing full to C:\Windows\system32\dump_20210520_121154.dmp
Complete

Notice I'm actually calling this for the application pool DATEMA ESC since I wanted to see that it actually worked for more than one result. Anyways to parse what's happening there at the end. The ForEach can take a script block which will be executed for each result in the previous call. Inside that script block is the call operator & which calls a command. I use it since dotnet-dump as far as I know isn't a PowerShell command and can't handle objects piped into it. The call operator first takes an optional path, the command or application, and last the arguments. I excluded the path, the command is "dotnet-dump", and the arguments is an array of strings which can be created in PowerShell with the @ character.

Since this achieved my goal I stopped there, but the above could be re-written as a script or possibly made into an alias, I really don't know. Here ends my PowerShell knowledge :)