86

I'm currently writing a library in C# and was using PowerShell to quickly test it on some occasions. However, this prevents me from re-building the project as PowerShell obviously still has the DLL open.

Is there a way of unloading the DLL again after adding it with Add-Type? The documentation doesn't seem to have clues on that and the obvious candidate would be Remove-Type (which doesn't exist – there is only one command anyway with Type as its noun). It gets cumbersome to close PowerShell and do all the stuff of navigating to the build directory and adding the type again each time I want to rebuild.

Joey
  • 344,408
  • 85
  • 689
  • 683
  • 2
    The documentation now says this: "You can't unload a type or change it." See https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/add-type#notes – Alan McBee Feb 01 '20 at 23:49
  • I sometimes create a one-letter alias in my `$Profile` that runs the script I'm testing, especailly in cases like @Joey points out. Using MS Terminal nowadays makes it easy to open/close tabs. Occasionally I'll skip the alias entirely and just make my `$Profile` execute the test script so that it runs as soon as a new tab is opened. – Paul π Dec 23 '22 at 23:29

6 Answers6

72

Like the others say, this is a .NET behavior. Assemblies loaded into an AppDomain cannot be unloaded. Only the AppDomain can be unloaded, and powershell uses a single appdomain. I blogged a bit about this some years ago:

https://web.archive.org/web/20170707034334/http://www.nivot.org/blog/post/2007/12/07/WhyAppDomainsAreNotAMagicBullet

When I test like this, I usually keep a shell open and use a nested shell to do tests. start powershell, cd to bin location then run "powershell" to start nested shell (new process.) "exit" to start over, and run "powershell" again.

x0n
  • 51,312
  • 7
  • 89
  • 111
  • @jpaugh unfortunately my hosting company deleted my free account, and I neglected to export my blog data. I've updated the link to archive dot org. – x0n Sep 13 '19 at 20:28
  • Sorry to hear that! I can say that https://logdown.com did not delete my free account; after several years of inactivity, I'm getting back into it. YMMV, but I recommend it. Edit: URL – jpaugh Sep 13 '19 at 21:11
55

I find the simplest way to get around this problem is to wrap the Add-Type and the test code inside of a Start-Job. Start-Job will create a background process, and the type will be loaded there. Once you are done, the process goes away and you're free to retry.

Here's an example of how it looks:

$job = Start-Job -ScriptBlock {

    Add-Type -path 'my.dll'
    $myObj = new-object My.MyTestClassName

    $result = $myObj.TestMethod
    $result
}
Wait-Job $job
Receive-Job $job

The output from the test method will be echoed to the console.

Katie Kilian
  • 6,815
  • 5
  • 41
  • 64
Start-Automating
  • 8,067
  • 2
  • 28
  • 47
  • 2
    I like this method, although in my case I need the result of a variable set in the background job. Without writing it to a file, I'm not sure how I would get that information. Nevertheless, I think it deserves a +1 – Slogmeister Extraordinaire Sep 22 '15 at 18:40
  • 2
    @SlogmeisterExtraordinaire In the job, echo the result. In the main script, call `Receive-Job` to fetch it. – Micha Wiedenmann Dec 17 '15 at 08:32
  • 2
    I added an example of how to do this. Any typos or mistakes in the example are mine alone, and not @Start-Automating's. – Katie Kilian Feb 01 '17 at 22:15
  • Thanks. This really helped me with confused loaded assemblies: https://stackoverflow.com/a/58417163/6466378 – halllo Oct 16 '19 at 15:46
  • Would have been nice to see this with the *Hello World* example above. – not2qubit Dec 30 '21 at 01:49
26

If your assembly doesn't require a binding context you can do this:

$bytes = [System.IO.File]::ReadAllBytes("Path_To_Your_Dll.dll")
[System.Reflection.Assembly]::Load($bytes)
Glorfindel
  • 21,988
  • 13
  • 81
  • 109
George Howarth
  • 2,767
  • 20
  • 18
  • and how this would made it possible to unload the assembly? – Farrukh Waheed Oct 11 '13 at 06:19
  • 7
    You wouldn't be able to unload, but OTOH the DLL will not be kept open and locked as the read has been independent of the load (`ReadAllBytes` doesn't keep the file open or locked). So it depends on what the OP wants; this will fix the problem of "I can't build" which seemed to be the driver for wanting an unload. – Chris J Feb 07 '14 at 13:20
  • This is nice. One small discrepancy/drawback, is that Add-Type seems to load the types in the indicated assembly and all of its dependencies, while this only loads the types from the indicated assembly. So if you need types from upstream assemblies, you'll need to use this to load them as well. – JLRishe Jun 21 '19 at 21:49
  • This works great! Even for DLLs I downloded into memory. No need to save them as a file first. Many thanks for sharing. – Carsten Aug 29 '20 at 09:01
6

Here is a complete example that allows to run the Add-Type command as a background job so that the assembly is unloaded once it finishes:

# Start-Job will not preserve the working directory, so do it manually
# Other arguments can also be passed to the job this way
$cd = Split-Path $MyInvocation.MyCommand.Path
$jobParams = @{
    'cd' = $cd
}

Start-Job -InputObject $jobParams -ScriptBlock {
    cd $Input.cd
    Add-Type -Path assembly.dll
} | Receive-Job -Wait -AutoRemoveJob

Receive-Job -Wait will make sure that the output of the job is received since otherwise it will be lost.

Knaģis
  • 20,827
  • 7
  • 66
  • 80
  • Can you clarify where the C# code goes, and what other parameters you need to change in your snippet? Also what is the `assembly.dll` you refer to? In my code there is no dll in the Powershell part. – not2qubit Jan 16 '22 at 11:32
0

I have been facing to similar problem. It is not possible to unload a type/assembly (that's because it applies to .NET framework).

In .NET you can solve it if you crate a new application domain (System.AppDomain) and load the assembly into that domain. It is possible to unload the app domain and that unloads all the dlls as well.

I haven't tried it yet, because for me it is much simpler to close a tab in Console and open new one.

stej
  • 28,745
  • 11
  • 71
  • 104
  • 1
    Powershell only uses a single appdomain, creating a new appdomain, loading types into that appdomain and then unloading the appdomain does not unload the type. – Scott Mackay Sep 04 '15 at 13:25
0

Visual Studio Code:

Settings -> Extensions -> PowerShell Configuration -> Debugging: Create Temporary Integrated Console

Check checkbox: "Determines whether a temporary PowerShell Integrated Console is created for each debugging sessions, usefull for debugging PowerShell classes and binary modules."

Darin
  • 1,423
  • 1
  • 10
  • 12