DEV Community

Cover image for How I Set Up an Awesome PowerShell Environment for Script Development
Emanuele Bartolesi for This is Learning

Posted on • Edited on

65 1 1 1 2

How I Set Up an Awesome PowerShell Environment for Script Development

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.

Image description

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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.

Final result

If everything run well, every time you open a new PowerShell instance, you should see something like that:

Image description


Dev Dispatch

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!

Image of Timescale

Timescale – the developer's data platform for modern apps, built on PostgreSQL

Timescale Cloud is PostgreSQL optimized for speed, scale, and performance. Over 3 million IoT, AI, crypto, and dev tool apps are powered by Timescale. Try it free today! No credit card required.

Try free

Top comments (9)

Collapse
 
kovesp profile image
Peter Koves • Edited

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

  1. Have Utilities and Business on PSModulePath
  2. Each module must be in its own directory under either Utilities or Business.
    • The directory must have the same name as the PSM1 file.
    • The directory must contain the PSM1 file as well as the corresponding PSD1 file.
    • The PSD1 file can be generated by the New-ModuleManifest cmdlet.
    • At the minimum, set the RootModuleentry to the name of the PSM1 file.
    • Each function must be listed in the FunctionsToExportentry. 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

foreach ($path in @('Utilites','Business') {
   foreach ($mod in Get-ChildItem $path -Directory) {
      Invoke-Expression "using module $($mod.Name)"
   }
}
Enter fullscreen mode Exit fullscreen mode
Collapse
 
mortiel profile image
Mortiel • Edited

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.

Collapse
 
kovesp profile image
Peter Koves

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 use Import-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 using Expand-Selection (Alt-Shift-RightArrow) multiple times.

Thread Thread
 
mortiel profile image
Mortiel

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.

Collapse
 
pascal_sala_6-25-24 profile image
Pascal Sala

Thank you, good article.

Collapse
 
kasuken profile image
Emanuele Bartolesi

thanks for the feedback.

Collapse
 
mortiel profile image
Mortiel • Edited

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.

Collapse
 
moodcave profile image
David

Fyi, looks like your ModuleDefinitions have the paths flipped in that code snippet.

Collapse
 
kasuken profile image
Emanuele Bartolesi

thanks for that!
I never saw it in my script, because at the end, it works anyway! 😂😂😂

Eliminate Context Switching and Maximize Productivity

Pieces.app

Pieces Copilot is your personalized workflow assistant, working alongside your favorite apps. Ask questions about entire repositories, generate contextualized code, save and reuse useful snippets, and streamline your development process.

Learn more

👋 Kindness is contagious

Dive into an ocean of knowledge with this thought-provoking post, revered deeply within the supportive DEV Community. Developers of all levels are welcome to join and enhance our collective intelligence.

Saying a simple "thank you" can brighten someone's day. Share your gratitude in the comments below!

On DEV, sharing ideas eases our path and fortifies our community connections. Found this helpful? Sending a quick thanks to the author can be profoundly valued.

Okay