Request-InfisicalCertificate -InstallChain could hang indefinitely. Adding a root certificate to CurrentUser\Root makes Windows raise a modal trust confirmation dialog, and X509Store.Add blocks until it is answered. When that dialog was hidden or the session non-interactive (scheduled task, MECM task sequence) the cmdlet appeared to stop right after installing the intermediate, with no indication why. A warning is now emitted before the blocking call. -StoreLocation now defaults to the process elevation when the caller does not supply it: LocalMachine when elevated, CurrentUser otherwise. This is what most callers want, and it sidesteps the trust prompt entirely because writing LocalMachine\Root already required elevation. Applied to both Request-InfisicalCertificate and Install-InfisicalCertificate; the resolved value is reported on the verbose stream and an explicit -StoreLocation wins. Chain routing is unchanged and already correct: self-signed certificates go to the Root store and everything else to CertificateAuthority, within whichever location was resolved. When the resolved location is LocalMachine and -KeyStorageFlags was not supplied, the private key is written to the machine key store. Without this the key lands in the calling user's profile while the certificate sits in LocalMachine\My, which is the usual cause of an installed certificate that reports no usable private key to a service. Reuse detection now searches the store location the install will write to rather than always searching CurrentUser, so -AllowRenewal and the existing certificate short-circuit behave consistently with where certificates land. Elevation detection moved to InfisicalCmdletBase (evaluated through the engine, since the module targets netstandard2.0 and carries no System.Security.Principal.Windows reference) and is shared with Write-InfisicalScepMdmProfileToWmi, which loses its private copy. README gains the fuller worked example, a genericized output transcript, and a "Where certificates get installed" section; cmdlet help updated to match. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
39 KiB
PSInfisicalAPI
A C# binary PowerShell module for interacting with the Infisical 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
Install-Module -Name PSInfisicalAPI -Scope CurrentUser
Import-Module -Name PSInfisicalAPI
From source
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 51 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. |
Organizations
| Cmdlet | Purpose |
|---|---|
Get-InfisicalOrganization |
Lists or retrieves Infisical organizations accessible to the current identity. |
New-InfisicalOrganization |
Creates a new Infisical organization. |
Update-InfisicalOrganization |
Updates the name or slug of an existing Infisical organization. |
Remove-InfisicalOrganization |
Deletes an Infisical organization. |
Sub-Organizations
| Cmdlet | Purpose |
|---|---|
Get-InfisicalSubOrganization |
Lists or retrieves Infisical sub-organizations, with optional search, paging, and ordering filters. |
New-InfisicalSubOrganization |
Creates a new Infisical sub-organization. |
Update-InfisicalSubOrganization |
Updates the name or slug of an existing Infisical sub-organization. |
Remove-InfisicalSubOrganization |
Deletes an Infisical sub-organization. |
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. |
Get-InfisicalSANList |
Builds a SAN candidate list (device name, <device>.<suffix> per adapter DNS suffix, RFC 1918 + CGNAT IPv4 addresses, IPv4/IPv6 loopback) for Request-InfisicalCertificate -DnsName. |
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 <Cmdlet> -Full for parameter details and Get-Help about_PSInfisicalAPI for the module overview.
Quick start
$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
End-to-end: request and install a chained certificate
Connects, selects a cert-manager project, sources SANs from Get-InfisicalSANList, requests a certificate through a certificate profile, installs it (and its chain) into the current-user store, and disconnects. Each call uses a splatted OrderedDictionary constructed with OrdinalIgnoreCase so parameter names round-trip case-insensitively.
This is the shape to use for fleet enrollment, where each machine needs its own common name. See Choosing an issuance path — a PKI subscriber is not the right tool for this, because it pins one fixed common name.
$ConnectInfisicalParameters = New-Object -TypeName 'System.Collections.Specialized.OrderedDictionary' -ArgumentList ([System.StringComparer]::OrdinalIgnoreCase)
$ConnectInfisicalParameters.BaseUri = 'https://app.infisical.com'
$ConnectInfisicalParameters.OrganizationId = '00000000-0000-0000-0000-000000000000'
$ConnectInfisicalParameters.ClientId = 'machine-identity-client-id'
$ConnectInfisicalParameters.ClientSecret = ConvertTo-SecureString -String 'ClientSecret' -AsPlainText -Force
$ConnectInfisicalParameters.PassThru = $True
$ConnectInfisicalParameters.Verbose = $True
$Connection = Connect-Infisical @ConnectInfisicalParameters
$Project = Get-InfisicalProject -Type cert-manager | Select-Object -First 1
$Project
#region Certificate authorities. Not required for profile issuance - the profile already binds its CA - but
# useful for confirming the chain you expect to be installed.
$CAList = Get-InfisicalCertificateAuthority -ProjectId ($Project.Id) -Kind Internal
$RootCA = $CAList | Where-Object {([String]::IsNullOrEmpty($_.ParentCaId) -eq $True)} | Select-Object -First 1
$RootCA
$IntermediateCA = $CAList | Where-Object {([String]::IsNullOrEmpty($_.ParentCaId) -eq $False)}
$IntermediateCA
#endregion
$CertificateProfile = Get-InfisicalCertificateProfile -ProjectId ($Project.Id) -IncludeConfigs |
Where-Object {($_.EnrollmentType -iin @('API')) -and ($_.Slug -imatch '.*Server.*')} |
Select-Object -First 1
$CertificateProfile
$SanList = Get-InfisicalSANList
$SanList
$RequestInfisicalCertificateParameters = New-Object -TypeName 'System.Collections.Specialized.OrderedDictionary' -ArgumentList ([System.StringComparer]::OrdinalIgnoreCase)
$RequestInfisicalCertificateParameters.ProjectId = $Project.Id
$RequestInfisicalCertificateParameters.CertificateProfileId = $CertificateProfile.Id
$RequestInfisicalCertificateParameters.CommonName = $Env:ComputerName.ToUpper()
$RequestInfisicalCertificateParameters.DnsName = New-Object -TypeName 'System.Collections.Generic.List[System.String]'
$RequestInfisicalCertificateParameters.DnsName.AddRange($SanList)
$RequestInfisicalCertificateParameters.DnsName.Add('app.contoso.com')
$RequestInfisicalCertificateParameters.DnsName.Add('api.contoso.com')
$RequestInfisicalCertificateParameters.DnsName.Add('boot.contoso.com')
$RequestInfisicalCertificateParameters.Ttl = '90d'
$RequestInfisicalCertificateParameters.Install = $True
$RequestInfisicalCertificateParameters.InstallChain = $True
$RequestInfisicalCertificateParameters.Verbose = $True
$Certificate = Request-InfisicalCertificate @RequestInfisicalCertificateParameters
$Null = Disconnect-Infisical -Verbose
Note $CertificateProfile rather than $Profile: $Profile is an automatic variable in PowerShell (the path to the current profile script), and assigning to it works but shadows something the host relies on.
-StoreName/-StoreLocation are omitted deliberately — see Where certificates get installed.
Example output
Id : 00000000-0000-0000-0000-000000000000
Name : Microsoft Endpoint Configuration Manager
Slug : mecm
Description :
OrganizationId : 11111111-1111-1111-1111-111111111111
Type : cert-manager
AutoCapitalization : False
EnvironmentSlugs : {dev, staging, prod}
CreatedAtUtc : 3/12/2026 8:32:52 PM +00:00
UpdatedAtUtc : 6/21/2026 7:00:28 PM +00:00
Name : root-ca
CommonName : Contoso Root Certificate Authority
Type : internal
Status : active
KeyAlgorithm : RSA_2048
NotAfter : 03/25/2036 00:00:00
Id : 22222222-2222-2222-2222-222222222222
Name : intermediate-ca
CommonName : Contoso Intermediate Certificate Authority
Type : internal
Status : active
KeyAlgorithm : RSA_2048
NotAfter : 03/25/2031 00:00:00
Id : 33333333-3333-3333-3333-333333333333
Id : 44444444-4444-4444-4444-444444444444
ProjectId : 00000000-0000-0000-0000-000000000000
CaId : 33333333-3333-3333-3333-333333333333
CertificatePolicyId : 55555555-5555-5555-5555-555555555555
Slug : serverauthentication
Description :
EnrollmentType : api
IssuerType : ca
EstConfigId :
ApiConfigId : 66666666-6666-6666-6666-666666666666
AcmeConfigId :
ScepConfigId :
CreatedAtUtc : 3/25/2026 4:53:53 PM +00:00
UpdatedAtUtc : 3/25/2026 4:53:53 PM +00:00
Defaults : PSInfisicalAPI.Models.InfisicalCertificateProfileDefaults
CertificateAuthority : PSInfisicalAPI.Models.InfisicalCertificateAuthoritySummary
CertificatePolicy :
ApiConfig : PSInfisicalAPI.Models.InfisicalCertificateProfileApiConfig
WEB01
10.20.30.40
WEB01.contoso.com
127.0.0.1
::1
VERBOSE: [...] - [Information] - [PkiClient] - Attempting to search Infisical certificates. Please Wait...
VERBOSE: [...] - [Verbose] - [HttpClient] - Attempting HTTP POST to https://infisical.contoso.com/api/v1/projects/00000000-0000-0000-0000-000000000000/certificates/search. Please Wait...
VERBOSE: [...] - [Verbose] - [HttpClient] - HTTP POST completed with status 200.
VERBOSE: [...] - [Information] - [PkiClient] - Infisical certificate search was successful.
VERBOSE: [...] - [Information] - [RequestInfisicalCertificateCmdlet] - Process is elevated; defaulting -StoreLocation to LocalMachine. Pass -StoreLocation explicitly to override.
VERBOSE: [...] - [Information] - [RequestInfisicalCertificateCmdlet] - Issuing via certificate profile '44444444-4444-4444-4444-444444444444' in project '00000000-0000-0000-0000-000000000000'.
VERBOSE: Performing the operation "Request new certificate" on target "certificate profile '44444444-4444-4444-4444-444444444444' for CN=WEB01".
VERBOSE: [...] - [Information] - [PkiClient] - Attempting to issue certificate via profile '44444444-4444-4444-4444-444444444444'. Please Wait...
VERBOSE: [...] - [Verbose] - [HttpClient] - Attempting HTTP POST to https://infisical.contoso.com/api/v1/cert-manager/certificates. Please Wait...
VERBOSE: [...] - [Verbose] - [HttpClient] - HTTP POST completed with status 200.
VERBOSE: [...] - [Information] - [PkiClient] - Infisical certificate issuance (profile) was successful.
VERBOSE: [...] - [Information] - [RequestInfisicalCertificateCmdlet] - Installed certificate to LocalMachine\My [F480A920DFB41EA8EE3E9178C1BC6A5EC7055B96].
VERBOSE: [...] - [Information] - [RequestInfisicalCertificateCmdlet] - Installed certificate to LocalMachine\CertificateAuthority [89A486A532D94EFE4391BEF2EA7F5E7E2B654AB0].
VERBOSE: [...] - [Information] - [RequestInfisicalCertificateCmdlet] - Installed certificate to LocalMachine\Root [1F3A77B0C2D45E6819AB3C7D0E5F2A9B4C81D6E7].
Where certificates get installed
When -StoreLocation is not supplied, the cmdlet picks it from the process's elevation, and says which it chose on the verbose stream:
| Session | Leaf | Intermediates | Roots |
|---|---|---|---|
| Elevated | LocalMachine\My |
LocalMachine\CertificateAuthority |
LocalMachine\Root |
| Not elevated | CurrentUser\My |
CurrentUser\CertificateAuthority |
CurrentUser\Root |
Chain members are routed by what they are, not by a single flag: a self-signed certificate goes to the trusted-root store, anything else to the intermediate store. Pass -StoreLocation explicitly to override the elevation default, and -StoreName to redirect the leaf.
When the resolved location is LocalMachine and -KeyStorageFlags was not supplied, the private key is written to the machine key store. Without that the key lands in the calling user's profile while the certificate sits in LocalMachine\My, which is the usual cause of an installed certificate that reports no usable private key to a service.
Non-elevated root installs prompt. Adding a root to
CurrentUser\Rootmakes Windows raise a modal trust dialog, and the call blocks until it is answered — if the dialog is hidden or the session is non-interactive (a scheduled task, an MECM task sequence), the cmdlet appears to hang indefinitely. It warns before blocking. Run elevated, or pass-StoreLocation LocalMachine, to install machine-wide with no prompt.
Choosing an issuance path
Request-InfisicalCertificate has three mutually exclusive issuance parameter sets. The deciding question is whether the common name varies per request:
| Parameter | Common name | Use when |
|---|---|---|
-CertificateProfileId |
Per request, constrained by policy | Fleet enrollment — many machines, each with its own CN. Works on any CA. |
-CertificateAuthorityId |
Per request, unconstrained | Fleet enrollment where no policy is wanted. Needs direct issuance on the CA. |
-PkiSubscriberSlug |
Fixed by the subscriber record | One named identity — a specific service or host, provisioned in advance. |
A PKI subscriber is a per-identity object, not a fleet template. signSubscriberCert rejects any CSR whose CN differs from the subscriber's:
Common name (CN) in the CSR does not match the subscriber's common name
It also allowlists SANs — every dNSName/email SAN in the CSR must appear in the subscriber's subjectAlternativeNames — and requires CSR key usages to be a subset of the subscriber's. (IP SANs are not covered by that check.) Enrolling N machines through subscribers therefore means creating N subscribers. Prefer a profile.
There is no -CertificateTemplateId parameter. Infisical's REST API exposes no template-based issuance route — templates are consumed internally by EST and subscribers — so when the API says "Certificate template or subscriber is required for issuance", the reachable answers are a profile, a subscriber, or direct issuance.
The cmdlet resolves and reports the issuer before generating a keypair, so -Verbose tells you exactly what will sign the request:
VERBOSE: [...] - [Information] - [RequestInfisicalCertificateCmdlet] - Issuing via certificate profile 'a1b2c3d4-...' in project '2122628e-...'.
VERBOSE: [...] - [Information] - [RequestInfisicalCertificateCmdlet] - Issuing directly via certificate authority 'intermediate-ca' (bf661d78-...); direct issuance is enabled.
-WhatIf names the same issuer without issuing anything:
Request-InfisicalCertificate @RequestInfisicalCertificateParameters -WhatIf
# What if: Performing the operation "Request new certificate" on target
# "certificate profile 'a1b2c3d4-...' for CN=WEB01".
Discovering profiles (recommended)
A certificate profile binds a CA to a certificate policy. The policy constrains the subject, key usages, and extended key usages with allowed/required/denied lists, so the common name still varies per request while staying inside guardrails.
Get-InfisicalCertificateProfile -ProjectId ($Project.Id) | Format-Table Id, Name, CaId
Get-InfisicalCertificatePolicy -ProjectId ($Project.Id) | Format-Table Id, Name
Profile issuance is the only path that does not consult the CA's direct-issuance flag — the service short-circuits it:
if (!isFromProfile && !ca.enableDirectIssuance && !certificateTemplate) { throw ... }
So a profile issues successfully against a CA whose EnableDirectIssuance is False. If a project has no profiles, create a policy then a profile under Certificate Management in the Infisical UI.
Discovering subscribers
Get-InfisicalPkiSubscriber -ProjectId ($Project.Id) |
Format-Table Name, CommonName, Status, Ttl, CaId
Pass the subscriber's Name to -PkiSubscriberSlug, and set -CommonName to exactly that subscriber's CommonName. Because the subscriber owns the lifetime and usage policy, -Ttl, -KeyUsage, and -ExtendedKeyUsage are not accepted on this parameter set — set them on the subscriber in Infisical instead.
An empty result means the project has no subscribers; create one under Certificate Management > Subscribers. This module is read-only for subscribers, so creation is UI or raw API (POST /api/v1/pki/subscribers).
Direct issuance on a CA
Direct issuance lets a CA sign a bare CSR with no profile, subscriber, or template in front of it. When it is off, Infisical rejects the request with 400 Certificate template or subscriber is required for issuance; this module catches that before building a CSR.
There is no UI toggle or API field for this.
enableDirectIssuanceappears in no create or update schema — the generic CA schemas accept onlynameandstatus. It is set at CA creation (the column defaults totrue) and is not editable afterwards through the public API.
A CA can therefore read False for a reason that is not obvious. Migration 20250521110635_add-external-ca-pki.ts renamed the older requireTemplateForIssuance column to enableDirectIssuance and inverted every existing value:
t.renameColumn("requireTemplateForIssuance", "enableDirectIssuance");
...
.update({ name: slugifiedName, enableDirectIssuance: !ca.enableDirectIssuance });
Any CA created before that migration with "require template for issuance" enabled now reads EnableDirectIssuance = False permanently. The options are to use a profile (which ignores the flag), or to create a new CA — new CAs default to true.
Get-InfisicalCertificateAuthority -ProjectId ($Project.Id) -Kind Internal |
Format-Table Name, CommonName, Status, EnableDirectIssuance
$Ca = Get-InfisicalCertificateAuthority -ProjectId ($Project.Id) -Kind Internal |
Where-Object {($_.EnableDirectIssuance -eq $True)} |
Select-Object -First 1
$RequestInfisicalCertificateParameters.CertificateAuthorityId = $Ca.Id
$RequestInfisicalCertificateParameters.Ttl = '90d' # required by the CA path
Subject and SAN handling
-CommonNametakes the bare value (WEB01.contoso.com), not an RDN.CN=WEB01is accepted and normalized, since the CSR builder adds theCN=prefix itself.Get-InfisicalSANListreturns DNS names and IP addresses in one list. Passing the whole list to-DnsNameis fine: IP literals are detected and emitted asiPAddressSAN entries rather than malformeddNSNameentries.-Ttl(or-NotAfter) applies to the-CertificateAuthorityIdand-CertificateProfileIdpaths. Subscriber-issued certificates take their lifetime from the subscriber definition.
Diagnostics and error handling
Every cmdlet derives from PSCmdlet, so the full set of common parameters is bound: -Verbose, -Debug, -ErrorAction, -ErrorVariable, -WarningAction, -WarningVariable, -InformationAction, -InformationVariable, -OutVariable, -PipelineVariable, and -WhatIf/-Confirm on the cmdlets that declare SupportsShouldProcess.
Output is routed by stream so those parameters mean what they say:
| Stream | Carries | Controlled by |
|---|---|---|
| Error | The failure itself, once, as a non-terminating ErrorRecord |
-ErrorAction, -ErrorVariable, 2> |
| Warning | Genuine advisories that are not failures (e.g. issuance returned no certificate) | -WarningAction, -WarningVariable |
| Verbose | Request/response trace and the diagnostic trail leading up to a failure | -Verbose |
| Debug | Low-level detail | -Debug |
A failed call surfaces exactly one error. The [Error]-tagged diagnostic lines that precede it are on the verbose stream, so they appear only under -Verbose and never compete with the ErrorRecord:
# One error, no warning noise.
Request-InfisicalCertificate @Parameters -ErrorVariable Failure -ErrorAction SilentlyContinue
# The ErrorRecord carries the API detail; no log scraping required.
$Failure[0].Exception.StatusCode # 400
$Failure[0].Exception.ApiErrorCode # BadRequest
$Failure[0].Exception.ApiErrorMessage # Certificate template or subscriber is required for issuance
$Failure[0].Exception.ApiRequestId # req-SSPFN1gc2zHvkV
-ErrorAction decides the outcome
Operation failures are reported as non-terminating errors, so -ErrorAction (or $ErrorActionPreference) governs what happens, exactly as it does for built-in cmdlets:
-ErrorAction |
Behavior |
|---|---|
Continue (default) |
Error is written; a pipeline keeps processing its remaining input |
SilentlyContinue |
Nothing is printed; the error is still in $Error and -ErrorVariable |
Ignore |
Nothing is printed and nothing is recorded in $Error |
Stop |
Promoted to a terminating error that try/catch catches |
Inquire |
Prompts |
A failing item does not abort the batch:
'web01', 'does-not-exist', 'web02' |
ForEach-Object { Get-InfisicalPkiSubscriber -ProjectId $ProjectId -Name $_ -ErrorAction SilentlyContinue }
# emits web01 and web02; the failure is available in $Error
To catch failures you must ask for it with -ErrorAction Stop or $ErrorActionPreference = 'Stop':
try {
$Certificate = Request-InfisicalCertificate @Parameters -ErrorAction Stop
} catch [PSInfisicalAPI.Errors.InfisicalApiException] {
Write-Warning "Issuance failed with HTTP $($_.Exception.StatusCode): $($_.Exception.ApiErrorMessage)"
}
Breaking change. Failures were previously terminating, so
try/catchcaught them without-ErrorAction Stop. Existingtry/catchblocks need-ErrorAction Stopadded (or$ErrorActionPreference = 'Stop'set) to keep catching.
Exception types are InfisicalApiException, InfisicalAuthenticationException, InfisicalHttpException, InfisicalSerializationException, InfisicalConfigurationException, InfisicalExportException, and InfisicalImportException, all deriving from InfisicalException.
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:
ProcessUserMachine
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
[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.
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
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.csdeclares aconst stringidentifier for each endpoint.InfisicalEndpointRegistry.csmaps each identifier to one or moreInfisicalEndpointDefinitionrecords grouped by resource (RegisterAuthentication,RegisterSecrets,RegisterPki, etc.).
To add a route:
- Add a constant in
InfisicalEndpointNames.cs(e.g.,public const string ListPkiSubscribers = "ListPkiSubscribers";). - In the matching
Register<Resource>method, callAdd(map, new InfisicalEndpointDefinition { ... })withName,Resource,Version,Method,Template, and theRequiresAuthorization/ContainsSecretMaterialInRequest/ContainsSecretMaterialInResponseflags. Use{placeholder}tokens inTemplate; they are substituted from thepathParametersdictionary passed by the caller. - If the same logical operation has more than one upstream path (legacy + current), register both definitions under the same
Name—InvokeWithCandidateFallbacktries each in order until one succeeds. - 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:
[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:
-
Update
build.ps1in two places — theCmdletsToExportarray inside the generated manifest block, and the$expectedCmdsarray used byTest-ModuleImports. Both must list the same cmdlets; the build fails fast if they drift. -
Add a
<command:command>entry inModule/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. -
For consolidated
List/ single-record cmdlets, ship three examples: two straight-line invocations (one per parameter set) and oneOrderedDictionarysplat. The splat must construct the dictionary withOrdinalIgnoreCaseso parameter names round-trip case-insensitively:$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 -
Add a
## Unreleasedentry toCHANGELOG.mddescribing the change (mark removals of public cmdlets or parameters as BREAKING). -
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:
- Stage and commit your source changes first. Suppose this produces commit
S. - Run
./build.ps1 -RunTests -CommitArtifacts. The build picks upSasHEAD, embeds it asBuildCommitHash, then stages and commits only the build outputs (Module/PSInfisicalAPI/bin/**,Module/PSInfisicalAPI/PSInfisicalAPI.psd1, and theCHANGELOG.mdbuild-stamp insertion). The commit message referencesSso the binary commit always traces back to its source. 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.