Your team's PowerShell automation is a dozen .ps1 files pasted around in chat. How would you turn that into modules the team installs, and what do you have to decide about hosting and versioning?
answer
- stop pasting, start publishing
- manifest gives it a version
- a feed the team registers once
- immutable versions or pinning is theatre
- publish from CI, not a laptop
basics
~20 sPackage the scripts as modules with manifests, then publish them to a repository the team registers once — a private NuGet feed or a file share — so installs and updates go through Install-Module. The decisions are hosting, trust policy, versioning contract, and who publishes.
solid answer
~50 sGroup the scripts into a small number of cohesive modules, each with a `.psd1` manifest carrying `ModuleVersion`, an explicit `FunctionsToExport` and any `RequiredModules`. That alone converts "paste this snippet" into "here is a versioned artifact". Then give it somewhere to live. `Register-PSRepository` (or `Register-PSResourceRepository` with the newer PSResourceGet) points the team at a private NuGet-compatible feed — Azure Artifacts, a self-hosted feed, or, for a small team, even a file share used as a repository. You publish with `Publish-Module -Repository Internal -NuGetApiKey ...`, ideally from CI on a tag rather than from a laptop; the team installs with `Install-Module -Repository Internal`. The judgment calls are the interesting part: internal feed versus vendoring into each repo; who is allowed to publish; whether the repository's `InstallationPolicy` is `Trusted` and what that means for review; and a versioning contract — semantic versioning, immutable published versions, breaking changes only on a major bump — because pinning downstream is worthless without it.
code
powershell · 9 linesRegister-PSRepository -Name Internal `
-SourceLocation 'https://internal/nuget/v2' `
-PublishLocation 'https://internal/nuget/v2/package' `
-InstallationPolicy Trusted
Publish-Module -Path .\Contoso.Inventory -Repository Internal -NuGetApiKey $key
Find-Module -Repository Internal
Install-Module -Name Contoso.Inventory -Repository Internal -Scope AllUsersgo deeper
Know that shared automation belongs in a module with a manifest rather than pasted scripts, and that Install-Module can point at an internal repository, not only the public gallery.
Be able to walk the mechanics end to end: write the manifest, Register-PSRepository, Publish-Module, Install-Module, and explain what a version number lets a consumer do.
Show the operational thinking — publishing from CI on a tag, choosing install scope per identity, and diagnosing why an install that works for a person fails for a service account.
Own the tradeoff and the contract: internal feed versus vendoring versus baked images, who may publish, immutable semantic versions as the promise that makes downstream pinning real, and a migration path when the tooling generation changes.
## Why the pasted-script model fails A script pasted into chat has no version, no dependency declaration, no ownership, and no way to answer "which copy is everyone running?". Every fix has to be re-pasted, and divergent copies drift silently. Turning it into a module is not ceremony — it buys identity, a version, a declared public surface and an install path. ## Step one: package Group by cohesion, not by author. A handful of modules along team-meaningful lines (`Contoso.Deploy`, `Contoso.Inventory`) beats one grab-bag or twenty micro-modules. Each becomes a folder with a `.psm1` (or several files dot-sourced by it) and a manifest: ```powershell @{ RootModule = 'Contoso.Inventory.psm1' ModuleVersion = '1.0.0' GUID = '...' FunctionsToExport = @('Get-HostInventory','Export-HostInventory') RequiredModules = @(@{ ModuleName='Foo'; ModuleVersion='2.1.0' }) PrivateData = @{ PSData = @{ Tags=@('inventory'); ProjectUri='https://internal/...' } } } ``` Enumerating `FunctionsToExport` matters twice: it declares the API you are willing to support, and it keeps command discovery fast for everyone who installs it. ## Step two: choose where it lives There are three realistic shapes, and the choice is a genuine tradeoff. **A private repository.** Register a NuGet-compatible feed — Azure Artifacts, a self-hosted feed, GitHub Packages — once per machine: ```powershell Register-PSRepository -Name Internal ` -SourceLocation 'https://internal/nuget/v2' ` -PublishLocation 'https://internal/nuget/v2/package' ` -InstallationPolicy Trusted Publish-Module -Path .\Contoso.Inventory -Repository Internal -NuGetApiKey $key Install-Module -Name Contoso.Inventory -Repository Internal -Scope AllUsers ``` This gives you real versioning, discovery via `Find-Module`, and a familiar workflow. The cost is a service to run and credentials to distribute. **A file share as a repository.** `-SourceLocation` accepts a UNC path. Cheap, no service, works for a small team on one network — but no authentication story beyond share permissions, and it does not travel to cloud agents. **Vendoring.** Skip the feed: each consuming repo restores exact versions with `Save-Module` at build time, or commits the module folder. Strongest reproducibility, no infrastructure, but updates become N pull requests instead of one publish. A mature setup usually ends up with a feed for people and vendoring or a baked image for build agents. ## Step three: the versioning contract Downstream pinning is only meaningful if versions mean something. Commit to three rules: semantic versioning, where a renamed parameter or a changed output shape is a major bump; published versions are immutable — never re-publish a number with different content; and a deprecation path where the old command keeps working for a release before it disappears. Without immutability, `-RequiredVersion` in a consumer's script is a lie. Where the version number comes from is its own decision. Hand-editing `ModuleVersion` in the manifest is honest and reviewable; deriving it from a git tag in CI removes the forget-to-bump failure. Either is defensible; leaving it at `1.0.0` forever is not. ## Step four: who publishes, and what trust means Publishing from a laptop means the artifact reflects one person's working tree. Publishing from CI on a tag means it reflects reviewed, tested source, and the API key lives in the pipeline rather than in a dozen profiles. That is the shape to argue for. `InstallationPolicy` is worth being precise about: `Trusted` suppresses the confirmation prompt on install; it is a convenience setting, not a security control, and it says nothing about whether the content is safe. What actually protects consumers is who can push to the feed, code review before a tag, and — if you go further — signed packages. Do not oversell the flag. ## Step five: the tooling generation `PowerShellGet` 2.x (`Register-PSRepository`, `Install-Module`, `Publish-Module`) is what ships in Windows PowerShell 5.1 and older PowerShell 7 releases and is still the lowest common denominator. `Microsoft.PowerShell.PSResourceGet` — bundled from PowerShell 7.4 — is the successor with `Register-PSResourceRepository`, `Install-PSResource` and `Publish-PSResource`. If your fleet spans both, standardise your documented commands on the set every machine has, and migrate deliberately rather than mixing them in one runbook. ## Step six: getting it onto the machines Humans install for themselves; automation identities do not read your chat message. Decide per context: `-Scope AllUsers` on shared servers, a bootstrap step in the CI job, or modules baked into the container image. Remember that a per-user install is invisible to a service account — the single most common reason "it worked on my machine" survives the move to modules. ## What an interviewer is listening for Not cmdlet recall. They want to hear that you separated packaging from hosting from versioning, that you named a real tradeoff (feed versus vendoring, laptop versus CI publishing), and that you were careful about what a trust setting does and does not guarantee.
- When would you skip the internal repository entirely and just vendor the module into each consuming repo?When there are few consumers, reproducibility matters more than update velocity, or the consumers are build agents that should not depend on network state. Vendoring with `Save-Module` makes the exact dependency part of the artifact and reviewable in a diff. The cost is that one fix becomes a pull request per consumer, which stops scaling once consumers are numerous.
- Does setting a repository's InstallationPolicy to Trusted make installing from it safe?No. It only suppresses the confirmation prompt. Safety comes from who can push to the feed, review before publish, and immutable versions — plus signing if you need provenance. Treating the flag as a security control is a common mistake; it is a convenience setting about prompting.
- How do you handle a breaking change to a module a dozen teams already use?Bump the major version, keep the previous version installable and unchanged, and communicate the migration with the old command still working for one release where feasible. Consumers pinned with `-RequiredVersion` are unaffected until they choose to move; consumers who never pin are the ones who discover the change, which is itself an argument for pinning in your published guidance.
- Where should the module version number come from?Either hand-edited in the manifest as a reviewed decision, or derived in CI from the release tag. The tag-driven approach removes the forgotten bump and guarantees the artifact matches reviewed source. What is not defensible is a version that never moves, because every downstream pin then refers to a moving target.
saying these in an interview costs you the question
- Says a shared network folder of .ps1 files is equivalent
- Treats InstallationPolicy Trusted as a security boundary
- Re-publishes the same version number with new content
- Never bumps ModuleVersion, so consumers cannot pin
- Publishes from a developer laptop with a shared API key