The Module That Almost Shipped to Production
A financial services client called us after a junior admin ran Install-Module on a domain controller during a maintenance window. The module pulled a dependency chain three layers deep, half of which had not been reviewed by anyone, and the change request had no rollback plan. Nothing broke that night, but the audit trail looked terrible the next morning. When we work with PowerShell Gallery modules in regulated environments, the install command is the easy part — the trust, scope, and version decisions around it are where engagements actually live or die.
PowerShell Gallery is a productivity multiplier, but treating it like a package manager with no governance is the same mistake teams make with public container registries. The defaults are conservative for a reason, and overriding them without a process is how supply chain incidents start.
Why Every Repository Is Untrusted by Default
The first time you run Install-Module -Name Pscx, PowerShell prompts you with an untrusted repository warning. That prompt is not bureaucratic noise — it is the secure-by-default philosophy of PowerShellGet doing exactly what it should. Even the official PSGallery is marked untrusted on a fresh install, which forces a human decision before code from the internet lands on a production host.
You can flip the trust flag with Set-PSRepository -Name PSGallery -InstallationPolicy Trusted, and Microsoft’s own documentation walks you through it on Microsoft Learn. My opinion: do not do this on servers. Trust the repository on your build agent or your packaging workstation, then ship vetted modules to production through your own internal feed. The blast radius of a compromised module is the same whether the prompt appeared or not, so the prompt is the cheapest control you have on workstations.
Scope, Version, and the Quiet Install Trap
The two parameters that prevent most accidental damage are -Scope and -RequiredVersion. Without them, Install-Module behaves differently depending on whether your shell is elevated, and you end up with modules in AllUsers when you wanted them isolated to your profile.
Install-Module -Name Az -Scope CurrentUser -RequiredVersion 11.5.0
Install-Module -Name Pester -Scope AllUsers -RequiredVersion 5.6.1 -Force
Pinning a version is the same discipline you would apply to a requirements.txt or a Terraform provider block. We pin every module that runs in a scheduled task or a CI pipeline for one of our managed services clients, because a silent minor version bump in Az.Storage once changed the default behavior of a cmdlet we relied on and broke a nightly backup verification job. The caveat is that pinning creates an obligation — somebody has to review and update those pins on a cadence, otherwise you accumulate stale modules with known CVEs.
Save-Module: The Pattern Nobody Teaches Juniors
The single most useful habit when evaluating a new module is to never install it first. Use Save-Module to download the package to a sandbox path that is not on $env:PSModulePath, then read what you got before importing it anywhere.
Save-Module -Name Timezone -Repository PSGallery -Path C:\testmodule
Get-ChildItem -Path C:\testmodule\ -Recurse
The module lands in a versioned folder — C:\testmodule\Timezone\1.2.2 — containing the .psd1 manifest, the .psm1 code, and any test files the author published. Open the .psd1, check the declared dependencies, scan the .psm1 for anything calling out to network endpoints or invoking native binaries, and only then promote it to a real module path. For a healthcare client we built this into the engineer onboarding checklist alongside our YAML parsing standards for PowerShell, because the same review discipline applies whether you are consuming data or executable code.
Updating Without Breaking the Last Known Good State
Update-Module is friendlier than people give it credit for. It installs the new version side by side with the existing one rather than overwriting it, and PowerShell automatically prefers the newest version at import time. That side-by-side behavior is your rollback plan: if the new version misbehaves, you can Import-Module -RequiredVersion the previous one without reinstalling anything.
Update-Module -Name Az
Get-Module -Name Az -ListAvailable | Select-Object Name, Version, Path
For PowerShell 5.1 hosts you have an extra prerequisite — the bundled PowerShellGet is too old to talk to the modern Gallery API, and TLS 1.2 needs to be enabled at the .NET level. The bootstrap is two lines:
Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force
Install-Module -Name PowerShellGet -Force -AllowClobber
Run that once per host before you do anything else, otherwise Find-Module will return cryptic errors that look like network problems but are actually protocol mismatches. We document this in every Windows build standard we hand to clients, right next to the remoting authentication choices that govern how those modules get invoked across the fleet.
A Workflow That Survives Audit
The pattern we deploy across managed environments looks the same whether the client has ten servers or two hundred. Engineers test modules with Save-Module in a sandbox, approved modules get republished to an internal NuGet feed registered with Register-PSRepository, and production hosts only trust that internal feed. PSGallery itself stays untrusted on every server, so a typo in a runbook cannot pull arbitrary code from the internet.
| Environment | PSGallery Trust | Source of Truth | Version Strategy |
|---|---|---|---|
| Engineer workstation | Trusted | PSGallery | Latest, manual |
| Build agent | Trusted | PSGallery | Pinned in manifest |
| Production server | Untrusted | Internal feed | Pinned, change-controlled |
This mirrors the same defense-in-depth thinking we apply to cloud supply chain risk, because a PowerShell module on a domain controller has the same privilege as a workload running with a service principal — the surface area is just less obvious. The NIST Cybersecurity Framework calls this out under the Supply Chain Risk Management category, and PowerShell module governance fits squarely inside it.
The Practical Takeaway
Treat Install-Module like git clone from an unknown remote. Use Save-Module to inspect, -Scope and -RequiredVersion to constrain, and an internal repository to gate what reaches production. If you want help designing that pipeline for your environment, get in touch with our team — module governance is one of the cheapest controls you can add to a Windows estate, and one of the most overlooked.


