Let's assume you are developing a PowerShell module. You would add small modifications to the module and test if they work as expected iteratively. In each iteration, after you edit the module code, how do you test the modification?
I like testing the module on the terminal interactively when I'm adding frequent and small changes in the early phase of development. I also do interactive testing if the module is designed for interactive usage, or if their results need to be checked on the terminal, such as modules for terminal customizations. However, there is an issue with module reloading.
Module Reloading is Hard
To reflect the code changes on the terminal, you have to run this command every time you edit the module:
Import-Module MyModule -Force
This is not enough sometimes. If your module has a nested module, and you are making changes to the nested module, you need to call Remove-Module
first.
Remove-Module TopLevelModule
Import-Module TopLevelModule
Even worse, if the module exports classes and it's imported by the using module
statement, you have to reboot the PowerShell session manually and import the module again. C# classes added by Add-Type
have the same issue.
In this example code, if you edit the message in the line [1]
or [2]
, you need to restart the PowerShell session to see the update.
# Test.psm1
class TestPSClass {
static [String] Run() {
return 'Hi' # [1]
}
}
Add-Type -TypeDefinition @'
public class TestCSClass {
public static System.String Run() {
return "Hello"; // [2]
}
}
'@
# On the terminal
using module ./Test.psm1
[TestPSClass]::Run()
[TestCSClass]::Run()
Typing Import-Module
or Remove-Module
many times might be okay but restarting the PowerShell session manually is a bit annoying. I have to automate this as a PowerShell enthusiast ๐ช.
RestartableSession Module
I made a module RestartableSession to reload modules properly no matter what kinds of modules they are. The module creates a nested session that can be easily restarted with a command. It also takes a ScriptBlock with -OnStart
parameter that is invoked every time the session is restarted.
Enter-RSSession
starts a new restartable session and invokes the specified ScriptBlock.
PS C:\> Enter-RSSession -OnStart {'Hello'}
Hello
RS(1) PS C:\>
Restart-RSSession
restarts the session and invokes the ScriptBlock again.
RS(1) PS C:\> Restart-RSSession
Hello
RS(2) PS C:\>
Start-RSRestartFileWatcher
is used to automatically call Restart-RSSession
on file changes under a folder.
RS(2) PS C:\> Start-RSRestartFileWatcher -Path D:\PathToFolder -IncludeSubdirectories
Let's see how these work for module reloading.
Automatic Module Reloading
The following code monitors the file changes in the module directory. When a file is modified, it automatically restarts the session and imports the module.
$onStart = {
Import-Module D:\PathToMyModule
Start-RSRestartFileWatcher -Path D:\PathToMyModule -IncludeSubdirectories
}
Enter-RSSession -OnStart $onStart
If you need using module
, you have to create the ScriptBlock from a string as it seems that the using
statement cannot be written in a ScriptBlock directly.
$onStartString =
@'
using module D:\PathToMyModule
Start-RSRestartFileWatcher -Path D:\PathToMyModule -IncludeSubdirectories
'@
$onStart = [ScriptBlock]::Create($onStartString)
Enter-RSSession -OnStart $onStart
Let's make a function so that it works for any module directory:
function Start-AutoReload($moduleDir) {
$onStartString =
@'
using module "{0}"
Start-RSRestartFileWatcher -Path "{0}" -IncludeSubdirectories
'@ -f $moduleDir
$onStart = [ScriptBlock]::Create($onStartString)
Enter-RSSession -OnStart $onStart
}
I have this function in my profile and at the beginning of the day, I run this command once on my terminal:
Start-AutoReload D:\PathToMyModule
Then, it becomes an auto-reloading terminal for the module. Once I've hit ctrl+s
to save the change, the terminal is ready to use with the module loaded on a fresh session.
The onStart
ScriptBlock can be anything so you can build a binary module or even run a Pester test there.
Alternative Way using VS Code
If you use the PowerShell extension in VS Code, you can use createTemporaryIntegratedConsole
debugger option to always create a new session when launching the debugger. By writing using module
inside the script specified by the script
option, you get a fresh session with your module loaded every time you launch the debugger.
// launch.json
"configurations": [
{
"name": "PowerShell: Module Interactive Session",
"type": "PowerShell",
"request": "launch",
"script": "${workspaceFolder}/Import.ps1",
"createTemporaryIntegratedConsole": true
}
]
# Import.ps1
using module ./Test.psm1
If you don't mind pressing F5 and waiting for the debugger, this approach should be good enough.
Conclusion
Module reloading sometimes requires a session restart which is a bit annoying in the development iteration. In this article, I shared a way to automate the reload process using the RestartableSession module.
Check out the repository page too and let me know if you come up with some other cool use cases of the module.
References
RestartableSession module
Reloading module does not reload submodules
https://github.com/PowerShell/PowerShell/issues/2505"Using module" statement does not reload module after changes are made
https://github.com/PowerShell/PowerShell/issues/7654