BREAKING: operation failures are now non-terminating errors, so -ErrorAction (and $ErrorActionPreference) decides the outcome. try/catch around these cmdlets now requires -ErrorAction Stop or $ErrorActionPreference = 'Stop'. A failing pipeline item no longer aborts the batch. Cmdlets no longer report "The pipeline has been stopped." as an error. Select-Object -First, and Where-Object feeding it, stop the upstream cmdlet by design; the shared error path in InfisicalCmdletBase now lets pipeline-control exceptions propagate untouched instead of logging them and raising an error. Error-level diagnostics moved off the warning stream to verbose. Every Logger.Error call site logs and then throws, so the failure already reaches the caller as an ErrorRecord; emitting it again as eight warning lines put failures under -WarningAction instead of -ErrorAction. One error per failure now. Request-InfisicalCertificate: - -CommonName accepts the RDN form (CN=WEB01) and reduces it to the bare value, which previously produced a CN=CN=WEB01 subject plus a bogus DNS SAN. - -DnsName routes IP literals to iPAddress SAN entries, so the mixed output of Get-InfisicalSANList can be splatted in as documented. - The CA path sends the normalized common name to the signing endpoint. - The issuance path is resolved and reported before a keypair is generated, and a CA with direct issuance disabled fails fast with guidance naming -PkiSubscriberSlug and -CertificateProfileId. Infisical exposes no template-based issuance route, so no -CertificateTemplateId is added. Get-InfisicalCertificateAuthority table output gains a DirectIssue column (EnableDirectIssuance) so CAs eligible for -CertificateAuthorityId are visible. README, about_PSInfisicalAPI, and cmdlet help document the stream/-ErrorAction contract, subscriber discovery, and direct-issuance setup. Adds regression tests for pipeline-stop propagation, logger stream routing, SAN splitting, common-name normalization, and the non-terminating convention across all cmdlets. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
30 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 PKI subscriber, 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.
$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 | Where-Object {($_.Name -eq 'Platform')} | Select-Object -First 1
$Subscriber = Get-InfisicalPkiSubscriber -ProjectId ($Project.Id) | Select-Object -First 1
$SanList = Get-InfisicalSANList
$RequestInfisicalCertificateParameters = New-Object -TypeName 'System.Collections.Specialized.OrderedDictionary' -ArgumentList ([System.StringComparer]::OrdinalIgnoreCase)
$RequestInfisicalCertificateParameters.ProjectId = $Project.Id
$RequestInfisicalCertificateParameters.PkiSubscriberSlug = $Subscriber.Name
$RequestInfisicalCertificateParameters.CommonName = $Env:ComputerName.ToUpper()
$RequestInfisicalCertificateParameters.DnsName = New-Object -TypeName 'System.Collections.Generic.List[System.String]'
$RequestInfisicalCertificateParameters.DnsName.AddRange($SanList)
$RequestInfisicalCertificateParameters.DnsName.Add('myrecord.mydomain.com')
$RequestInfisicalCertificateParameters.Install = $True
$RequestInfisicalCertificateParameters.InstallChain = $True
$RequestInfisicalCertificateParameters.Verbose = $True
$Certificate = Request-InfisicalCertificate @RequestInfisicalCertificateParameters
$Null = Disconnect-Infisical -Verbose
Choosing an issuance path
Request-InfisicalCertificate has three mutually exclusive issuance parameter sets. Which one works depends on how the project is configured in Infisical:
| Parameter | Use when |
|---|---|
-PkiSubscriberSlug |
The project defines PKI subscribers (Get-InfisicalPkiSubscriber). Preferred for most setups. |
-CertificateProfileId |
The project issues through certificate profiles (Get-InfisicalCertificateProfile). |
-CertificateAuthorityId |
Signing straight against a CA. Requires direct issuance to be enabled on that CA. |
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 subscriber, a profile, or enabling 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 PKI subscriber 'web-tier' 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
# "PKI subscriber 'web-tier' for CN=WEB01".
Discovering subscribers
A subscriber is a named enrollment identity that pins the CA, TTL, key usages, and SAN policy, so the request carries only a CSR. List what a project offers:
Get-InfisicalPkiSubscriber -ProjectId ($Project.Id) |
Format-Table Name, CommonName, Status, Ttl, CaId
Pass the subscriber's Name to -PkiSubscriberSlug. 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.
If the project has no subscribers, either create one (Certificate Management > Subscribers > Add Subscriber) or use one of the other two paths.
Enabling direct issuance on a CA
Direct issuance lets a CA sign a bare CSR with no subscriber or template in front of it. It is a per-CA setting, and Infisical rejects the request with 400 Certificate template or subscriber is required for issuance when it is off. This module now catches that before building a CSR:
Request-InfisicalCertificate : Certificate authority 'intermediate-ca' (bf661d78-...) has direct issuance
disabled, so it cannot sign a CSR on its own. Either enable direct issuance on the CA in Infisical
(Certificate Authorities > the CA > Enable Direct Issuance), or issue through a subscriber or profile
instead: Request-InfisicalCertificate -PkiSubscriberSlug <name> ... or -CertificateProfileId <id> ...
To turn it on, in the Infisical UI open the cert-manager project, go to Certificate Authorities, select the CA, and enable Direct Issuance in its settings. The setting is surfaced by this module as EnableDirectIssuance, so you can confirm it and pick an eligible CA in one step:
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
Prefer a subscriber or profile for routine enrollment: direct issuance bypasses the naming and key-usage constraints those layers enforce. Reserve it for bootstrap or break-glass cases.
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.