# 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 42 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. | | `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. | ### Process | Cmdlet | Purpose | | ------------------------ | -------------------------------------------------------------------------------------------------- | | `Start-InfisicalProcess` | Launches a child process with Infisical secrets injected directly into its environment block, capturing stdout/stderr and validating the exit code. | Use `Get-Help -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` 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 `` entry in `Module/PSInfisicalAPI/en-US/PSInfisicalAPI.dll-Help.xml`. Each entry must include a non-empty `` synopsis (do not let it start with the cmdlet name — the validation gate rejects PowerShell's auto-generated fallback), a non-empty `` body, and at least one `` with a non-empty `` 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 "`); 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).