Skip to content

PowerShell Module

Developer guidance for loading, iterating on, and testing the JIM PowerShell module from source in the devcontainer.

Looking for end-user guidance?

For installation, authentication, and cmdlet reference material aimed at administrators using the module against a deployed JIM instance, see PowerShell Module under the PowerShell Module section.

The module lives at src/JIM.PowerShell/ and is a plain script module: no compilation, no packaging step. During development you import it directly from the source tree and iterate in place.

Loading the module from source

From the repository root:

pwsh
Import-Module ./src/JIM.PowerShell/JIM.psd1

After editing a cmdlet, re-import with -Force to pick up the change:

Import-Module ./src/JIM.PowerShell/JIM.psd1 -Force

If -Force is not enough (for example, after renaming a function or changing the manifest), remove and re-import:

Remove-Module JIM
Import-Module ./src/JIM.PowerShell/JIM.psd1

Testing a branch on your host machine

Some module behaviour cannot be exercised from inside the devcontainer and must be tested from a PowerShell session on your host machine:

  • OS credential-store persistence: the refresh-token store writes to Windows Credential Manager, the macOS login Keychain, or Linux libsecret. The devcontainer has no usable keyring, so persistence always falls back to in-memory.
  • Silent reconnect across sessions: verifying that a brand-new terminal reconnects without a browser only makes sense where the token actually persists.
  • Interactive browser-based SSO: as noted under Connecting to local JIM, the browser flow only works on the host.

To test the branch's module on your host, copy the source tree into your user module path under the name JIM so a new terminal autoloads it. Importing straight from the repo path works for a single session, but copying into the module path is what lets a fresh terminal pick it up, which is exactly what the silent-reconnect tests need.

$dest = "$HOME/.local/share/powershell/Modules/JIM"
Remove-Item $dest -Recurse -Force -ErrorAction SilentlyContinue
Copy-Item <repo>/src/JIM.PowerShell $dest -Recurse
Import-Module JIM -Force
$dest = "$env:USERPROFILE\Documents\PowerShell\Modules\JIM"
Remove-Item $dest -Recurse -Force -ErrorAction SilentlyContinue
Copy-Item <repo>\src\JIM.PowerShell $dest -Recurse
Import-Module JIM -Force

Replace <repo> with your host checkout path. Confirm the loaded copy is the one you just placed, not a gallery copy elsewhere on the path:

(Get-Module JIM).Path

The copied folder shadows the gallery version

While the dev folder sits in your module path it takes precedence over any gallery-installed JIM, and both report the same ModuleVersion (the branch does not bump it). Get-Module JIM alone cannot tell them apart; use (Get-Module JIM).Path to be sure which one is loaded. After editing module source on the branch, re-run the Copy-Item step before Import-Module JIM -Force, since -Force reloads from the copied location, not the repo.

When finished, remove the dev copy and restore the published module. Deleting the folder matters: Remove-Module only unloads it from the current session, so a new terminal would silently reload the dev build while the folder remains on the path.

Remove-Module JIM -Force -ErrorAction SilentlyContinue
Remove-Item "$HOME/.local/share/powershell/Modules/JIM" -Recurse -Force
Install-Module JIM        # or Update-Module JIM if previously installed
Remove-Module JIM -Force -ErrorAction SilentlyContinue
Remove-Item "$env:USERPROFILE\Documents\PowerShell\Modules\JIM" -Recurse -Force
Install-Module JIM        # or Update-Module JIM if previously installed

Connecting to local JIM

Start the Docker stack first:

jim-stack

Then from a PowerShell session, connect with an API key. The devcontainer ships with an infrastructure API key generated in .env under JIM_INFRASTRUCTURE_API_KEY; the simplest approach is to read it directly from .env:

$apiKey = (Get-Content .env | Where-Object { $_ -match '^JIM_INFRASTRUCTURE_API_KEY=' }) -replace '^JIM_INFRASTRUCTURE_API_KEY=', ''
Connect-JIM -Url "http://localhost:5200" -ApiKey $apiKey

You can also paste the value directly:

Connect-JIM -Url "http://localhost:5200" -ApiKey "jim_ak_xxxxxxxxxxxx"

The infrastructure key is the right one for dev

JIM provisions the infrastructure API key automatically when JIM_INFRASTRUCTURE_API_KEY is set in .env; it shows as Infrastructure in the JIM web UI under Admin > API Keys. It is intended exactly for this kind of local automation and scripting. You can create additional personal keys via the UI or New-JIMApiKey once you are connected, but for day-to-day cmdlet development the infrastructure key is enough.

JIM_INFRASTRUCTURE_API_KEY is consumed by docker-compose at stack startup, not exported to your shell; that is why Connect-JIM -ApiKey $env:JIM_INFRASTRUCTURE_API_KEY does not work out of the box.

Verify with:

Test-JIMConnection

Use API keys from inside the devcontainer

Interactive browser-based SSO (Connect-JIM -Url "http://localhost:5200" with no -ApiKey) does not work from a PowerShell session running inside the devcontainer.

The browser-based flow needs the PowerShell session to (a) resolve localhost:8181 to the bundled Keycloak container and (b) open a browser on the user's machine to complete sign-in. The devcontainer is a separate container with no access to the host's localhost:8181 port binding and no display, so both prerequisites fail.

The browser-based flow does work from a PowerShell session on the host machine (outside any container), because that session can reach the port-forwarded http://localhost:8181 Keycloak endpoint and has a browser available. The devcontainer's bundled Keycloak realm includes a jim-powershell public client with loopback redirect URIs and docker-compose.override.yml sets JIM_SSO_PUBLIC_AUTHORITY and JIM_SSO_PUBLIC_CLIENT_ID so /api/v1/auth/config advertises values the host can use.

For inside-the-devcontainer development, always use an API key.

Module layout

Path Purpose
src/JIM.PowerShell/JIM.psd1 Module manifest. Declares FunctionsToExport, ModuleVersion, minimum PowerShell version, and metadata
src/JIM.PowerShell/JIM.psm1 Module loader. Auto-discovers every .ps1 under Public/ and Private/ and dot-sources them
src/JIM.PowerShell/Public/ Exported cmdlets, grouped into subfolders by area (Connection/, Security/, Metaverse/, etc.). Every .ps1 here must have a matching entry in FunctionsToExport
src/JIM.PowerShell/Private/ Internal helpers. Auto-loaded but not exported
src/JIM.PowerShell/Tests/ Pester test suites. One .Tests.ps1 file per cmdlet area

Auto-discovery in JIM.psm1 means you do not need to edit the .psm1 when adding a cmdlet. Only the manifest (JIM.psd1) needs updating to export the new function.

Adding a new cmdlet

  1. Create the file under the appropriate area folder, using verb-noun naming:

    src/JIM.PowerShell/Public/Security/Add-JIMRoleMember.ps1
    
  2. Include the copyright header, a complete comment-based help block (synopsis, description, parameters, examples, notes), and the function body. See an existing cmdlet such as Connect-JIM.ps1 for the expected shape.

  3. Add the function name to FunctionsToExport in JIM.psd1, under the correct grouping comment.
  4. Create the matching Pester test in src/JIM.PowerShell/Tests/<Area>.Tests.ps1.
  5. Reload the module (Import-Module ./src/JIM.PowerShell/JIM.psd1 -Force) and invoke the new cmdlet interactively to sanity-check.

Test first

JIM follows TDD. Write the Pester test before the cmdlet implementation; it must fail before the function exists, then pass once you have written the minimum code to satisfy it. See Testing for the full workflow.

Running Pester tests

From the repository root:

jim-test-ps

This runs every .Tests.ps1 file in src/JIM.PowerShell/Tests/ through Pester with detailed output.

jim-test-all runs the .NET test suite and the Pester suite in sequence and produces a combined summary. Use it for the final pre-PR check when your change touches both sides.

To target a single test file, invoke Pester directly:

pwsh -NoProfile -Command "Import-Module Pester; Invoke-Pester -Path ./src/JIM.PowerShell/Tests/Security.Tests.ps1 -Output Detailed"

Further Reading