Files
PSInfisicalAPI/README.md
T
GraceSolutions 183fb48c32 Wire SCEP MDM cmdlets into manifest, build, help, and docs
Adds Get-/Export-/Write-InfisicalScepMdmProfile(ToWmi) to CmdletsToExport in the module manifest and to the build.ps1 manifest template and expected-cmdlet probe. Adds MAML help entries (description, notes, two examples each with an OrderedDictionary splat) for all three cmdlets. Updates README's cmdlet count from 34 to 37 and the cmdlet table with one-line descriptions. CHANGELOG entry summarizes the new feature, the default SCEP URL pattern, the elevation/platform guards, and the export-vs-throw rule for -Force.
2026-06-04 17:47:00 -04:00

253 lines
17 KiB
Markdown

# PSInfisicalAPI
A C# binary PowerShell module for interacting with the [Infisical](https://infisical.com/) REST API. It provides cmdlets for authentication, secret retrieval, structured export, and includes automatic environment-variable discovery so connections can be established with little or no inline configuration.
- License: AGPL-3.0
- Author: Grace Solutions
- Target framework: .NET Standard 2.0 (compatible with Windows PowerShell 5.1 and PowerShell 7+)
## Installation
### From the PowerShell Gallery
```powershell
Install-Module -Name PSInfisicalAPI -Scope CurrentUser
Import-Module -Name PSInfisicalAPI
```
### From source
```powershell
git clone https://prod.git.gracesolution.info/gsadmin/PSInfisicalAPI.git
cd PSInfisicalAPI
pwsh -NoProfile -ExecutionPolicy Bypass -File .\build.ps1 -RunTests
Import-Module -Name .\Module\PSInfisicalAPI
```
## Cmdlets
The module exports 37 cmdlets. Discovery cmdlets (`Get-Infisical*`) use a `List` (default) / single-record parameter-set pair: invoking without the identity parameter returns the collection, supplying the identity parameter returns one record.
### Session
| Cmdlet | Purpose |
| ---------------------- | -------------------------------------------------------------------------------------------------- |
| `Connect-Infisical` | Establishes an authenticated session with an Infisical server and stores it for use by subsequent cmdlets. |
| `Disconnect-Infisical` | Clears the current Infisical session from the module-level session manager. |
### Secrets
| Cmdlet | Purpose |
| ------------------------------------- | -------------------------------------------------------------------------------------------------- |
| `Get-InfisicalSecret` | Lists or retrieves Infisical secrets within a project, environment, and optional folder path. |
| `New-InfisicalSecret` | Creates a new Infisical secret, with support for SecureString values and bulk creation. |
| `Update-InfisicalSecret` | Updates an existing Infisical secret value, comment, name, or tags. |
| `Remove-InfisicalSecret` | Deletes one or many Infisical secrets by name. |
| `Copy-InfisicalSecret` | Duplicates one or more secrets into a different environment or secret path. |
| `ConvertTo-InfisicalSecretDictionary` | Converts a stream of InfisicalSecret objects into a name-keyed Dictionary of SecureString or plain text values. |
| `Export-InfisicalSecrets` | Exports InfisicalSecret objects to disk or environment variables in a chosen file format. |
### Projects
| Cmdlet | Purpose |
| ------------------------- | -------------------------------------------------------------------------------------------------- |
| `Get-InfisicalProject` | Lists or retrieves Infisical projects accessible to the current identity. |
| `New-InfisicalProject` | Creates a new Infisical project in the active organization. |
| `Update-InfisicalProject` | Updates the name, description, or auto-capitalization flag on an existing project. |
| `Remove-InfisicalProject` | Deletes an Infisical project. |
### Environments
| Cmdlet | Purpose |
| ----------------------------- | -------------------------------------------------------------------------------------------------- |
| `Get-InfisicalEnvironment` | Lists or retrieves Infisical environments defined on a project. |
| `New-InfisicalEnvironment` | Creates a new environment on an Infisical project. |
| `Update-InfisicalEnvironment` | Updates the name, slug, or sort order of an existing Infisical environment. |
| `Remove-InfisicalEnvironment` | Deletes an Infisical environment from a project. |
### Folders
| Cmdlet | Purpose |
| ------------------------ | -------------------------------------------------------------------------------------------------- |
| `Get-InfisicalFolder` | Lists or retrieves Infisical folders at a given secret path. |
| `New-InfisicalFolder` | Creates a new Infisical folder under the supplied parent path. |
| `Update-InfisicalFolder` | Renames an existing Infisical folder. |
| `Remove-InfisicalFolder` | Deletes an Infisical folder and all secrets it contains. |
### Tags
| Cmdlet | Purpose |
| --------------------- | -------------------------------------------------------------------------------------------------- |
| `Get-InfisicalTag` | Lists or retrieves Infisical tags defined on a project. |
| `New-InfisicalTag` | Creates a new Infisical tag on a project. |
| `Update-InfisicalTag` | Updates the slug, name, or color of an existing Infisical tag. |
| `Remove-InfisicalTag` | Deletes an Infisical tag from a project. |
### PKI
| Cmdlet | Purpose |
| ----------------------------------- | -------------------------------------------------------------------------------------------------- |
| `Get-InfisicalCertificateAuthority` | Lists or retrieves Infisical internal Certificate Authorities. |
| `Get-InfisicalPkiSubscriber` | Lists or retrieves Infisical PKI subscribers in a project. |
| `Get-InfisicalCertificate` | Lists or retrieves Infisical certificates in a project, with optional filters and automatic paging. |
| `Search-InfisicalCertificate` | Searches Infisical certificates with advanced filters and automatic paging. |
| `Request-InfisicalCertificate` | Requests a new Infisical certificate (local CSR + sign) or reuses a still-valid existing one. |
| `ConvertTo-InfisicalCertificate` | Materializes an X509Certificate2 from an Infisical certificate record, bundle, or serial number. |
| `Install-InfisicalCertificate` | Installs an Infisical certificate (and optional chain) into a Windows certificate store. |
| `Uninstall-InfisicalCertificate` | Removes a certificate from a Windows certificate store by thumbprint, subject, or pipeline input. |
| `Export-InfisicalCertificate` | Exports an Infisical certificate to disk in PEM, PFX, or CER format. |
| `Get-InfisicalScepMdmProfile` | Projects an Infisical certificate profile into a Windows SCEP MDM profile model. |
| `Export-InfisicalScepMdmProfile` | Writes a SCEP MDM profile to disk as a SyncML payload suitable for MDM delivery. |
| `Write-InfisicalScepMdmProfileToWmi`| Submits a SCEP MDM profile to the local MDM Bridge WMI provider to trigger enrollment. |
Use `Get-Help <Cmdlet> -Full` for parameter details and `Get-Help about_PSInfisicalAPI` for the module overview.
## Quick start
```powershell
$secureSecret = Read-Host -AsSecureString 'Client Secret'
$connection = Connect-Infisical `
-BaseUri 'https://app.infisical.com' `
-OrganizationId '00000000-0000-0000-0000-000000000000' `
-ProjectId '11111111-1111-1111-1111-111111111111' `
-Environment 'dev' `
-ClientId 'machine-identity-client-id' `
-ClientSecret $secureSecret `
-PassThru
Get-InfisicalSecret -SecretPath '/'
Disconnect-Infisical
```
## Automatic environment-variable discovery
When `Connect-Infisical` is invoked with one or more parameters missing (or set to whitespace/empty), the cmdlet searches environment variables and uses the first value it finds. This makes invocation as simple as `Connect-Infisical` when variables are set up in advance.
### Scope precedence
Scopes are searched in order; the first matching variable with a non-blank value wins:
1. `Process`
2. `User`
3. `Machine`
### Patterns
The resolver matches case-insensitively against patterns aligned with Infisical's CLI defaults plus common variants such as `CLOUDINIT_INFISICAL_*` and custom-prefixed names (e.g., `myapp_infisical_client_id`).
| Parameter | Example variable names matched |
| ----------------- | ------------------------------------------------------------------------------------ |
| `BaseUri` | `INFISICAL_API_URL`, `INFISICAL_BASE_URL`, `INFISICAL_HOST` |
| `OrganizationId` | `INFISICAL_ORG_ID`, `INFISICAL_ORGANIZATION_ID` |
| `ProjectId` | `INFISICAL_PROJECT_ID`, `INFISICAL_WORKSPACE_ID` |
| `Environment` | `INFISICAL_ENVIRONMENT`, `INFISICAL_ENV`, `INFISICAL_ENV_SLUG` |
| `ClientId` | `INFISICAL_CLIENT_ID`, `INFISICAL_UNIVERSAL_AUTH_CLIENT_ID` |
| `ClientSecret` | `INFISICAL_CLIENT_SECRET`, `INFISICAL_UNIVERSAL_AUTH_CLIENT_SECRET` |
| `AccessToken` | `INFISICAL_TOKEN`, `INFISICAL_ACCESS_TOKEN`, `INFISICAL_AUTH_TOKEN` |
| `SecretPath` | `INFISICAL_SECRET_PATH`, `INFISICAL_DEFAULT_SECRET_PATH` |
| `ApiVersion` | `INFISICAL_API_VERSION` |
Sensitive values (`ClientSecret`, `AccessToken`) are read directly into a read-only `SecureString` and never logged.
### Zero-configuration example
```powershell
[Environment]::SetEnvironmentVariable('INFISICAL_API_URL', 'https://app.infisical.com', 'User')
[Environment]::SetEnvironmentVariable('INFISICAL_ORG_ID', '00000000-0000-0000-0000-000000000000', 'User')
[Environment]::SetEnvironmentVariable('INFISICAL_PROJECT_ID', '11111111-1111-1111-1111-111111111111', 'User')
[Environment]::SetEnvironmentVariable('INFISICAL_ENVIRONMENT', 'dev', 'User')
[Environment]::SetEnvironmentVariable('INFISICAL_CLIENT_ID', 'machine-identity-client-id', 'User')
[Environment]::SetEnvironmentVariable('INFISICAL_CLIENT_SECRET', 'super-secret-value', 'User')
Connect-Infisical
Get-InfisicalSecret
```
### Mixed example (explicit values override discovery)
Explicit parameters always win over discovered values; blank/whitespace explicit values trigger discovery.
```powershell
Connect-Infisical -Environment 'prod' # everything else discovered from environment
```
### Logging
The resolver emits a single verbose line announcing the scan and one informational line per discovered variable (variable name and scope; values are never logged). Use `-Verbose` to see the scan announcement.
## Building
```powershell
pwsh -NoProfile -ExecutionPolicy Bypass -File .\build.ps1 -RunTests
```
The script builds the binary, runs unit tests, publishes binaries into `Module/PSInfisicalAPI/bin/`, regenerates the manifest, and validates that the module imports.
## Extending the module
### Adding a new API endpoint
All HTTP routes live in two files under `src/PSInfisicalAPI/Endpoints/`:
- `InfisicalEndpointNames.cs` declares a `const string` identifier for each endpoint.
- `InfisicalEndpointRegistry.cs` maps each identifier to one or more `InfisicalEndpointDefinition` records grouped by resource (`RegisterAuthentication`, `RegisterSecrets`, `RegisterPki`, etc.).
To add a route:
1. Add a constant in `InfisicalEndpointNames.cs` (e.g., `public const string ListPkiSubscribers = "ListPkiSubscribers";`).
2. In the matching `Register<Resource>` method, call `Add(map, new InfisicalEndpointDefinition { ... })` with `Name`, `Resource`, `Version`, `Method`, `Template`, and the `RequiresAuthorization` / `ContainsSecretMaterialInRequest` / `ContainsSecretMaterialInResponse` flags. Use `{placeholder}` tokens in `Template`; they are substituted from the `pathParameters` dictionary passed by the caller.
3. If the same logical operation has more than one upstream path (legacy + current), register both definitions under the same `Name``InvokeWithCandidateFallback` tries each in order until one succeeds.
4. Invoke the endpoint from the appropriate client (`InfisicalPkiClient`, `InfisicalSecretsClient`, etc.) via `_invoker.InvokeWithCandidateFallback(connection, InfisicalEndpointNames.XYZ, "XYZ", pathParameters, query, body)`.
### Adding a new cmdlet
Cmdlets live in `src/PSInfisicalAPI/Cmdlets/` and derive from `InfisicalCmdletBase`, which exposes `HttpClient`, `Logger`, `ResolveProjectId`, and `ThrowTerminatingForException`. Follow the consolidated discovery pattern when the cmdlet supports both list and single-record retrieval:
```csharp
[Cmdlet(VerbsCommon.Get, "InfisicalPkiSubscriber", DefaultParameterSetName = "List")]
[OutputType(typeof(InfisicalPkiSubscriber))]
public sealed class GetInfisicalPkiSubscriberCmdlet : InfisicalCmdletBase
{
[Parameter(ParameterSetName = "ByName", Mandatory = true, Position = 0, ValueFromPipelineByPropertyName = true)]
[Alias("SubscriberName", "Slug")]
public string Name { get; set; }
[Parameter] public string ProjectId { get; set; }
protected override void ProcessRecord() { /* dispatch on ParameterSetName */ }
}
```
After adding (or removing) a cmdlet:
1. Update `build.ps1` in **two** places — the `CmdletsToExport` array inside the generated manifest block, and the `$expectedCmds` array used by `Test-ModuleImports`. Both must list the same cmdlets; the build fails fast if they drift.
2. Add a `<command:command>` entry in `Module/PSInfisicalAPI/en-US/PSInfisicalAPI.dll-Help.xml`. Each entry must include a non-empty `<maml:description>` synopsis (do not let it start with the cmdlet name — the validation gate rejects PowerShell's auto-generated fallback), a non-empty `<maml:description>` body, and at least one `<command:example>` with a non-empty `<dev:code>` block.
3. For consolidated `List` / single-record cmdlets, ship **three examples**: two straight-line invocations (one per parameter set) and one `OrderedDictionary` splat. The splat must construct the dictionary with `OrdinalIgnoreCase` so parameter names round-trip case-insensitively:
```powershell
$Params = New-Object -TypeName 'System.Collections.Specialized.OrderedDictionary' -ArgumentList ([System.StringComparer]::OrdinalIgnoreCase)
$Params.ProjectId = (Get-InfisicalProject | Select-Object -First 1).Id
$Result = Get-InfisicalPkiSubscriber @Params
```
4. Add a `## Unreleased` entry to `CHANGELOG.md` describing the change (mark removals of public cmdlets or parameters as **BREAKING**).
5. Run `./build.ps1 -RunTests`. The script enforces the cmdlet list, runs the xUnit suite, and verifies that every exported cmdlet has a valid synopsis, description, and at least one non-empty example.
### Committing source and build artifacts in lockstep
The embedded `BuildCommitHash` in `Module/PSInfisicalAPI/PSInfisicalAPI.psd1` and the bundled DLL is captured from `git rev-parse HEAD` at build time. To keep the embedded hash truthful, commit source and build artifacts as two ordered commits:
1. Stage and commit your source changes first. Suppose this produces commit `S`.
2. Run `./build.ps1 -RunTests -CommitArtifacts`. The build picks up `S` as `HEAD`, embeds it as `BuildCommitHash`, then stages and commits **only** the build outputs (`Module/PSInfisicalAPI/bin/**`, `Module/PSInfisicalAPI/PSInfisicalAPI.psd1`, and the `CHANGELOG.md` build-stamp insertion). The commit message references `S` so the binary commit always traces back to its source.
3. `git push`.
`-CommitArtifacts` only touches the three artifact paths above; any other dirty files in your working tree are left alone. Use the older `-CommitOnSuccess` switch only when you intentionally want a single commit covering everything (`git add -A` + `git commit -m "Build <version>"`); the two switches are mutually exclusive.
## Continuous integration
`.gitea/workflows/publish-psgallery.yml` publishes the module to the PowerShell Gallery whenever a pull request is merged into `main`. The workflow expects a repository secret named `PSGALLERY_API_KEY` containing a valid Gallery API key.
## License
Distributed under the GNU Affero General Public License v3.0. See [LICENSE](LICENSE).