Over the past few years, I’ve had the opportunity to help colleagues write and refine PowerShell scripts for a variety of tasks. While I’m not a full-time PowerShell scripter, far from it, in fact, having a reliable and productive environment has been crucial. PowerShell scripting often comes into play when I need to automate a process, troubleshoot an issue, or quickly deliver a solution.
Without the right setup, even simple scripts can turn into a frustrating time sink. That’s why I’ve focused on creating an environment that works seamlessly, boosts productivity, and minimizes the effort required to get started. In this post, I’ll share how I’ve set up my PowerShell development environment to make scripting efficient and enjoyable—even when it’s not my main focus during the day.
Folders
First of all, on my pc, I have two local folders, called Utilities and Business.
Inside these folders I have some PowerShell modules I created or downloaded.
I have different kind of modules, for compressing a folder, for reading a CSV file and so on.
These modules help me to be more productive during the development or the debug and I don't have every time to remember which module I have to import or similar.
Add the modules to the profile
As I mentioned before, I don't want to import all the modules every time, manually in my script.
So, I changed the profile script to include all the modules in that folders, when PowerShell is loaded.
For changing the profile, you can open a terminal and write:
code $Profile
Now, inside the new window of Visual Studio Code, you can paste the following code:
Write-Host "Initializing Modules..." -ForegroundColor Cyan
# Ensure any errors during execution halt the script
$ErrorActionPreference = "Stop"
# Define module paths and exclusions
$ModuleDefinitions = @{
Utilities = @{
Path = "D:\_TEMP_\PowerShell\Utilities"
}
Business = @{
Path = "D:\_TEMP_\PowerShell\Business"
}
}
# Iterate through each module definition
foreach ($moduleName in $ModuleDefinitions.Keys) {
$moduleDetails = $ModuleDefinitions[$moduleName]
$modulePath = $moduleDetails.Path
# Update PSModulePath to include the current module path
if (-Not ($env:PSModulePath -contains $modulePath)) {
$env:PSModulePath = @(
$env:PSModulePath
$modulePath
) -join [System.IO.Path]::PathSeparator
}
# Load modules from the specified path, excluding unwanted directories
Get-ChildItem -Path $modulePath -Directory -Exclude @(".git")|
ForEach-Object {
Write-Host "Loading Module '$($_.Name)' from '$moduleName'" -ForegroundColor Green
try {
Import-Module $_.Name -ErrorAction Stop
} catch {
Write-Host "Failed to load module '$($_.Name)': $_" -ForegroundColor Red
}
}
}
Write-Host "Module loading completed!" -ForegroundColor Cyan
With the code above, every time I launch a PowerShell session, I have all my utilities and modules loaded correctly.
Install the PowerShell Extension for VS Code
The PowerShell extension enhances VS Code with features like IntelliSense, debugging, and code snippets.
Installation Steps:
- Open VS Code.
- Go to the Extensions view by clicking on the square icon in the Activity Bar or pressing Ctrl+Shift+X.
- Search for "PowerShell" and click "Install" on the extension by Microsoft.
Other utilities
For the terminal set up, you can follow one of my previous post about it.
Transforming Windows Terminal into a Productivity Powerhouse
Emanuele Bartolesi for This is Learning ・ Dec 27 '24
Final result
If everything run well, every time you open a new PowerShell instance, you should see something like that:
If you enjoyed this blog post and want to learn more about C# development, you might be interested in subscribing to my bi-weekly newsletter called Dev Dispatch. By subscribing, you will get access to exclusive content, tips, and tricks, as well as updates on the latest news and trends in the development world. You will also be able to interact with me, and share your feedback and suggestions. To subscribe, simply navigate to https://buttondown.email/kasuken?tag=devto, enter your email address and click on the Subscribe button. You can unsubscribe at any time. Thank you for your support!
Top comments (9)
IMHO, it is better to have 'using module ModuleName' for the modules used in a script. I want to know the dependencies of each script. As well, using takes effect at compilation time, wheras Import-Module happens at run-time.
For 'using module ...' to work seamlessly (using your environment as an example) you need to
New-ModuleManifest
cmdlet.RootModule
entry to the name of the PSM1 file.FunctionsToExport
entry. The other...ToExport
entries should be self-explanatory. Classes do not need an entry.If you insist on importing everything from the profile you can use
There is upsides and downsides to "using module..." that I've found.
Mainly, "using module" is absolutely required if you use custom classes in a script module, as custom classes do not load with Import-Module.
However, if you have to script using Windows PowerShell 5.1 (example: scripting for Windows Server with security restrictions), I've encountered bugs with "using module" that were fixed in later PowerShell Core versions.
I suppose that's an argument for creating compiled DLL modules, but I digress.
The main takeaway is that you should be very selective with the custom modules you load using the PSProfile as opposed to in the actual scripts, but instead only adding custom modules directories to PSModulePath is generally good to make "using" or "Import-Module" not require a full directory path.
Agreed. I was using
Import-Module
until I placed my first class in a module and spent a few hours trying to understand why my class was not being seen.The fact that having
using module
in a script will not reload the modified class when running the script in a session still bites. If only functions/cmdlets are modified in the module, one can useImport-Module -Force
(ipmo -fo
) to reload the module. But if a class is changed, a new session is needed. Which is a pain in VS Code: 3 clicks to restart the session + losing variables set in it.The only alternative is to select the class and use
F8
. But selecting a class isn't quite straight forward in VS Code; at best it involves usingExpand-Selection
(Alt-Shift-RightArrow
) multiple times.Using VS Code, you can pin the PowerShell menu to the status bar, which reduces the number of clicks needed down to 2, but better than using ISE, I suppose.
The other method is writing the module in .NET/C# as a DLL, which allows creating namespaces and more class capabilities. Sadly, I'm not extremely knowledgeable with C# and haven't really needed to go that route yet.
Thank you, good article.
thanks for the feedback.
If I remember correctly, I think the environmental variable $env:PSModulePath is a string value not an array/collection, so technically the if condition that uses -contains won't work as intended. You have to split the variable by the ';' delimiter into an array first.
$PSModulePathArray = $env:PSModulePath -split ';'
However, you're not seeing issues because the if condition is a negative match... "If ($string1
-contains $string2)" will always be false, so the negative of that will always be true, yeah?
Restart a PowerShell session multiple times and check if your custom "Business" and "Utilities" paths are in the PSModulePath variable multiple times.
Fyi, looks like your ModuleDefinitions have the paths flipped in that code snippet.
thanks for that!
I never saw it in my script, because at the end, it works anyway! 😂😂😂