Seeding a real instance failed with "Route GET:/api/v2/organizations not found".
The route genuinely does not exist: /api/v2/organizations mounts only
/:organizationId/* sub-routes, and the listing lives on the singular
GET /api/v1/organization, which returns the same { organizations: [...] } shape.
The module had the same defect, so Get-InfisicalOrganization would have 404d on
any current server. ListOrganizations now prefers /api/v1/organization with the
v2 route retained as a fallback candidate, and the organization client invokes
with candidate fallback so the fallback can actually be reached. The endpoint
shape test expected the broken template and now expects the working one.
The script reported this as "The supplied -AccessToken was rejected ... it may
have expired", which sent the reader after a token that was seconds old and
perfectly valid. A 404 or a connection failure says nothing about the token, so
only a 401 or 403 now produces that message; anything else reports that the
instance could not be reached and states plainly that it is not a token problem.
The token probe also moves to /api/v1/projects, which is the next call the
script makes anyway, so a successful probe proves both the token and a route the
run actually depends on rather than one chosen only for validation.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The module was read-only for PKI configuration, which is why standing up an
environment meant raw REST rather than cmdlets. Adds 15: New-, Set-, and Remove-
for certificate authorities, policies, profiles, applications, and PKI
subscribers.
New-InfisicalCertificateAuthority -Type Intermediate returns an authority that
can actually issue. Infisical creates a subordinate with status
pending-certificate and never invokes generateIntermediateCaCertificate from the
create path, so the cmdlet performs the remaining sequence: read the certificate
signing request, sign it with -ParentCaId, import the signed certificate and
chain back.
Policy and profile bodies are deeply nested, so the constraint objects and
per-enrollment-type config blocks are taken as dictionaries rather than as
dozens of parameters, matching how -Subject and -Metadata already work. Two
conversion details matter and are now pinned by tests:
- PowerShell callers capitalise hashtable keys, and @{ Required = ... } was
reaching the API as "Required", which its schema does not recognise. The
constraint vocabulary is emitted lower-cased while every other key keeps its
camelCase, since those are API field names supplied verbatim and lowercasing
ttlDays or isCA would silently drop them.
- An empty collection is omitted rather than sent. "allowed": [] reads to
Infisical as "allow nothing", never what @{ Allowed = @() } was meant to
express, and an entry carrying only "type" is rejected outright.
-EnrollmentConfig routes to the block matching -EnrollmentType so EST, ACME, and
SCEP settings arrive through one parameter. -AutoRenew is sent only when bound,
because a switch is false when absent and sending it unconditionally would
disable renewal on an update that never mentioned it.
-ProjectId is optional on all 15 and resolves as it does elsewhere. Every one
supports -WhatIf; Remove- cmdlets default to high confirm impact.
Also fixes Update-Changelog, which inserted the version heading above the notes
so the section the release workflow extracts held only the build line while the
notes stayed under Unreleased - every release published an empty changelog.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Update-Changelog inserted the version heading above the notes, so the section
the release workflow extracts contained only the build line while the actual
notes stayed under "Unreleased". Every release published an empty changelog. It
now promotes whatever sits under Unreleased into the version section and leaves
a fresh empty Unreleased above it, with the provenance line italicised and last
so it does not read as another entry in whichever section the notes ended on.
Re-running the same version remains a no-op, and a build with nothing to say
still produces a section.
SignCertificateByCa now prefers /api/v1/cert-manager over the older /api/v1/pki
route.
RetrieveCertificate and GetCertificateBundle deliberately keep the older route
first, which is the opposite of what it looks like. The /api/v1/pki route
resolves a certificate by serial number, which is what callers supply; the
cert-manager route takes a certificate ID and passes it through as
getCert({ id }), so it cannot answer a serial. Both remain registered so either
identifier resolves - which is what lets the metadata path look a certificate up
by ID - and a comment now records why reordering them would break serial
lookups.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
An organization that has not set up Certificate Manager has nothing to list, so
resolution now returns nothing instead of throwing, and the PKI Get-* cmdlets
return quietly. -Verbose explains what happened and what to do:
-ProjectId was not supplied and this organization has no Certificate Manager
project, so there is nothing to resolve to. Create one in Infisical
(Certificate Management), or pass -ProjectId to target a specific project.
Without the guard the null would have travelled to the client layer and
surfaced as "ProjectId is required" from somewhere unrelated to the cause, so
each Get-* cmdlet returns as soon as resolution comes back empty.
Request-InfisicalCertificate deliberately proceeds, because profile issuance
does not need a project: only the reuse search does, and that already degrades
to a local match with a warning when the search cannot run.
Resolution is now attempted once per cmdlet instance rather than once per
non-null result, so an organization with no project does not re-query on every
call.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Several Certificate Manager projects in one organization is a normal
configuration, not an ambiguity to reject. Infisical designates one as the
organization's active project and serves certificate applications only from it:
if (req.internalCertManagerProjectId !== activeProjectId) {
throw new BadRequestError({ message: "Applications are only available on
this organization's active Certificate Manager project." });
}
So an application-centric workflow is single-project by design, and resolving to
the active project is what makes it work. Resolution now picks that project when
several exist, falling back to the first - saying so on the verbose stream -
when the organization designates none. Only an organization with no Certificate
Manager project at all still errors, because there is genuinely nothing to
resolve to.
Adds InfisicalOrganization.DefaultCertManagerProjectId, which is what the
organization record calls its active project, so the choice is read rather than
guessed.
Moves Get-InfisicalProject onto /api/v1/projects. /api/v1/workspace mounts
Infisical's deprecated project router; it is retained as a fallback candidate so
older servers keep working, and the endpoint shape test now expects the current
route.
The end-to-end README example drops to the four calls that actually do the work:
find the application, pick its profile, gather SANs, request. The project lookup
is gone because -ProjectId resolves itself, and the CA lookup is gone because the
profile already binds its issuing CA and -InstallChain installs the chain
regardless.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The Infisical console never asks which Certificate Manager project to use. The
project is in the URL, but it is chosen implicitly - the service resolver takes
the single cert-manager project when an organization has exactly one - and
everything below it is presented as applications. Requiring -ProjectId on every
PKI cmdlet made the module stricter than the service it wraps.
-ProjectId is now optional on Get-InfisicalCertificateApplication,
-ApplicationEnrollment, -Authority, -Certificate, -CertificatePolicy,
-CertificateProfile, Get-InfisicalPkiSubscriber, and
Request-InfisicalCertificate. Omitting it applies the same rule the server
applies and reports the resolved project on the verbose stream; an organization
with several produces an error naming the candidates rather than guessing.
Resolution is client-side rather than deferred to the server because several PKI
endpoints carry the project in the URL path - /api/v1/projects/{projectId}/
pki-subscribers and .../certificates/search - and cannot fall back to the
server's resolver at all. Doing it in one place keeps path-scoped and
query-scoped endpoints behaving identically.
Get-InfisicalCertificate -SerialNumber no longer resolves a project, since
addressing a certificate by serial does not need one.
Adds -ApplicationId and -CaId to Get-InfisicalCertificateProfile, which the
profiles endpoint already supports as query filters, so a listing can be scoped
the way the console groups profiles. Get-InfisicalCertificate already accepted
-ApplicationId. The existing five-argument ListCertificateProfiles overload is
retained.
Also fixes the README Quick Start, which did not run: it passed -ProjectId and
-Environment to Connect-Infisical, which has neither, failing with "A parameter
cannot be found that matches parameter name 'ProjectId'". Project, environment,
and secret path are per-call parameters. Documents that a project contains
applications, since listing projects returning one entry while the console shows
several applications is the confusing part.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Takes any IDictionary, so a hashtable, an [Ordered] dictionary, or a generic
Dictionary[String,String] all bind. Verified against all three issuance
parameter sets under Windows PowerShell 5.1.
The reconcile is client-side by necessity. Infisical's PATCH /certificates/{id}
replaces a certificate's metadata wholesale - certificate-v3-service deletes
every resource_metadata row for the certificate before inserting what it was
sent - so sending just the caller's keys would silently discard everything else
attached to it. The module reads the current set, merges the supplied keys over
it, and writes back the union. Keys it was not asked about survive, and when
nothing would change no request is sent at all.
Reconciliation runs on the reuse path too, so changing metadata does not force
a reissuance to take effect.
Values are flattened to strings because the API accepts only strings: 443
becomes "443", $True becomes "True", $Null becomes "". Keys are trimmed and
compared case-insensitively, matching how PowerShell callers supply them, and
blank keys are dropped since the API rejects them.
Metadata never fails an issuance that otherwise succeeded. By the time it is
applied the certificate exists and may already be installed in the store, so a
failure is reported as a warning and the certificate is still emitted.
Adds InfisicalCertificate.Metadata and InfisicalCertificateResult.Metadata as
case-insensitive dictionaries, the PATCH endpoint under both the cert-manager
and pki route namespaces, and models the metadata array already returned by the
certificate response.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Extending -DnsName or -IpAddress and re-running returned the existing
certificate, which lacked the name that had just been added - the reuse check
compared only the common name. A candidate must now carry every requested name,
and the name that disqualified it is reported so the reissue is explainable.
Reading SANs back off an installed certificate needs a decoder: netstandard2.0
has no X509SubjectAlternativeNameExtension, and X509Extension.Format produces
localized text that cannot be compared. The extension is decoded from its DER
bytes with BouncyCastle, already carried for CSR generation.
The rule is coverage rather than equality, since a certificate carrying more
names than requested still satisfies the request. DNS names compare
case-insensitively and IP addresses are normalized through IPAddress, so ::1 and
0:0:0:0:0:0:0:1 are the same name. Trimming the SAN set therefore still reuses;
-Force covers that case.
FindMatch keeps its original four-argument overload so existing callers are
unaffected, and only reports a rejected name when no candidate qualified.
Verified against a live CurrentUser\My store with a certificate carrying
SANPROBE, SANPROBE.contoso.com and 10.20.30.40: identical and subset requests
reuse, a new DNS name or IP forces reissue naming the missing entry, differing
case reuses, and an empty request reuses.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Switching certificate profiles returned the existing certificate instead of
issuing a new one. The reuse check searched Infisical by common name and status
only, so a host already holding a server-authentication certificate for its own
name was handed that certificate back when asking a client-authentication
profile - same subject, wrong extended key usages.
The search is now scoped by -CertificateProfileId or -CertificateAuthorityId.
Both filters already existed on InfisicalCertificateSearchQuery and serialize as
profileIds/caIds; the reuse path simply never set them. The subscriber path
needs no filter because a subscriber pins its own common name, so matching the
name is already equivalent to matching the subscriber.
A second defect compounded it: InfisicalLocalCertificateLookup.FindMatch only
applies its serial filter when the candidate set is non-empty, so a search that
legitimately returned nothing degraded into a name-only local match - exactly
the case that hands back another issuer's certificate. A completed search that
finds nothing is now a definite "nothing to reuse".
The lenient fallback is kept for the case where Infisical cannot be reached,
since failing a renewal because the API is down is worse, but it now announces
itself as a warning rather than being silent.
Reuse still does not compare subject alternative names; that gap is documented
with -Force as the workaround rather than half-addressed here.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Chain members are now installed before the leaf, so the certificate is
chainable the moment it appears in the store rather than momentarily orphaned.
After -InstallChain the chain is validated against the machine's own stores.
An incomplete result is reported as a warning naming the certificate whose
issuer is missing, which is the exact condition Windows surfaces as "The issuer
of this certificate could not be found" - previously that was only discoverable
in certmgr after the fact.
Chain routing is unchanged and already handles arbitrary depth: a self-signed
certificate is a root and goes to the trusted-root store, anything with an
issuer above it is a subordinate CA and goes to the intermediate store. Only
the leaf honours -StoreName (default My). This is now stated in the docs,
because the split was not obvious.
The installed certificate's Windows friendly name defaults to the common name
in upper case, which is what operators look for in certmgr. -FriendlyName
overrides it and moves from the ByCa parameter set to all of them; the CA path
still forwards the same value to Infisical as the issued certificate's
friendlyName.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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>
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>
InfisicalSecretsClient.List now merges dto.Imports[].Secrets with dto.Secrets using local-wins precedence, restoring imported secrets that were previously dropped when -IncludeImports was set.
Get-InfisicalSecret, ConvertTo-InfisicalSecretDictionary, Export-InfisicalSecrets, Import-InfisicalSecret, New/Update/Remove-InfisicalSecret, and Start-InfisicalProcess now emit Information-level counts for retrieved/processed/injected items.
All collection-returning Get-* cmdlets (Folder, Project, Organization, Environment, Tag, SubOrganization, Certificate, CertificateAuthority, CertificateApplication, CertificatePolicy, CertificateProfile, PkiSubscriber) now log returned counts.
Get-InfisicalEnvironmentVariable gains an optional -Scope (EnvironmentVariableTarget) parameter plus per-scope verbose tracing and Information-level found/not-found outcome lines.
Renames -Prefix to -SecretsPrefix and -ForcePrefix to -ForceSecretsPrefix across ConvertTo-InfisicalSecretDictionary, Import-InfisicalSecret, Export-InfisicalSecrets, and Start-InfisicalProcess. Start-InfisicalProcess also renames the pipeline parameter -Secret to -Secrets. The previous names remain available as parameter aliases (Prefix, ForcePrefix, Secret) for backward compatibility. Internal InfisicalProcessOptions properties renamed to match.
Adds 8 cmdlets for Organization and Sub-Organization CRUD (Get/New/Update/Remove for each), targeting /api/v2/organizations and /api/v1/sub-organizations. Get cmdlets default to List parameter set and switch to Single when -OrganizationId or -SubOrganizationId is supplied. New/Update/Remove honor -WhatIf/-Confirm; Remove defaults to High ConfirmImpact and supports -PassThru. No project context required.
Adds Get-InfisicalSANList: emits a deduplicated SAN candidate set containing the local device name, the device name suffixed with each non-empty DNS suffix found across operational adapters and the system primary domain, every IPv4 unicast address falling within RFC 1918 or CGNAT, and the IPv4/IPv6 loopback addresses. Supports optional case-insensitive -InclusionExpression and -ExclusionExpression regex filters applied in fetch -> include -> exclude -> output order. Output is a single strongly-typed System.String[] array emitted non-enumerated so List<string>.AddRange consumes it directly.
Registers 10 new endpoints, adds InfisicalOrganization/InfisicalSubOrganization models with DTOs, mappers, and clients, full MAML help for all 9 new cmdlets, mapper unit tests, EndpointRegistry inline-data coverage, and docs/DesignSpec.md sections 16.7 and 16.8. build.ps1 CmdletsToExport and Test-ModuleImports expected list now contain 51 cmdlets. README updated with Organization/Sub-Organization tables, the new Get-InfisicalSANList entry, and an end-to-end certificate request example using splatted OrderedDictionary blocks.
- New cmdlet Start-InfisicalProcess: launches a child process with InfisicalSecret
objects decrypted directly into ProcessStartInfo.Environment (optional -Prefix),
additional -EnvironmentVariables, stdout/stderr capture, -AcceptableExitCodeList,
-ParsingExpression regex parsing, -ExecutionTimeout / -ExecutionTimeoutInterval,
-NoWait, -WindowStyle / -CreateNoWindow parameter sets, -Priority,
-StandardInputObjectList, -SecureArgumentList, -LogOutput, -ContinueOnError, and
ShouldProcess support. Secret plaintext is never written to user or machine scope.
- Stream capture uses event-based OutputDataReceived/ErrorDataReceived with
BeginOutputReadLine/BeginErrorReadLine (no Task / ReadToEndAsync /
GetAwaiter().GetResult()) to avoid PowerShell SynchronizationContext deadlocks.
- Restored the do { log; sleep } while (!HasExited) polling pattern using
Thread.Sleep(pollInterval) so verbose "has been running for X" / "Checking again
in Y" messages fire at the configured cadence even when no -ExecutionTimeout is
supplied.
- TimeSpan values in verbose logs and on the result now use a friendly format
("7 seconds, and 364 milliseconds", "1 minute, and 30 seconds", "N/A" when zero)
matching the legacy Start-ProcessWithOutput GetTimeSpanMessage scriptblock.
- Added DurationFriendly property to InfisicalProcessResult and a "The command
execution took X" verbose line at completion.
- build.ps1 CmdletsToExport and Test-ModuleImports expected list contain 42 cmdlets.
- Added 9 xUnit tests covering FormatFriendly singular/plural, multi-unit joining,
zero, sub-millisecond, and skip-zero-components behavior.
Adds an optional [string] -Prefix parameter that prepends the supplied
string to every emitted variable name, regardless of -Format
(Json/Yaml/Xml/Env/EnvironmentVariables). When omitted or empty the
exporter buffer is forwarded unchanged (no-op).
Implementation clones each InfisicalSecret with SecretName = Prefix +
SecretName so the caller's pipeline objects are never mutated; the
SecureString and Tags/SecretMetadata array references are shared
(read-only usage downstream).
Also updates the cmdlet help XML description + adds a -Prefix example,
and reflects the new parameter in docs/DesignSpec.md.
Adds opt-in insecure-transport controls for pre-production / self-signed
scenarios. Configured once on Connect-Infisical and persisted on the
InfisicalConnection; every downstream cmdlet inherits via the base class.
Connect-Infisical:
- [switch] SkipCertificateCheck Disable TLS chain validation per request.
- [switch] AllowInsecureTransport Permit http:// BaseUri (else throw).
- Logs explicit Warning records when either is enabled.
InfisicalConnection:
- New SkipCertificateCheck / AllowInsecureTransport bool properties (default
false). Persisted on the session for downstream cmdlets.
InfisicalCmdletBase:
- HttpClient getter now constructs InfisicalHttpClient with the flag derived
from a new virtual ShouldSkipCertificateCheck(), which reads the current
session. Connect-Infisical overrides it to use its own switch since the
session does not yet exist during auth.
InfisicalHttpClient:
- New skipCertificateCheck ctor parameter; when on, sets
HttpWebRequest.ServerCertificateValidationCallback per request via
reflection (property is available at runtime on PS 5.1/7 but not surfaced
by netstandard2.0). Falls back to ServicePointManager with a warning if
reflection is unavailable.
Tests:
- InfisicalConnection defaults both flags to false.
- ShouldSkipCertificateCheck reads from InfisicalSessionManager.Current.
The non-self-signed chain-routing test called DateTimeOffset.UtcNow.AddDays(1)
twice -- once for the root cert and once for the intermediate cert -- so when
the wall clock ticked a second between the two calls the intermediate's
notAfter ended up later than the root's notAfter, and CertificateRequest.Create
rejected it:
System.ArgumentException : The requested notAfter value (...:11) is later
than issuerCertificate.NotAfter (...:10). (Parameter 'notAfter')
Capture notBefore/notAfter once at the top of the test and reuse the same
DateTimeOffset for both certificates so the intermediate's validity window
is guaranteed equal to (not later than) the issuer's.
TESTS
- 216/216 passing locally; this was reliably reproducible under CI load
(latest failure on commit ceea76255b).
The -List switch was a no-op marker on the default parameter set of six
Get-* cmdlets. Each cmdlet was declared with DefaultParameterSetName = "List"
and the -List switch was the only parameter unique to that set, so it served
no purpose beyond visual decoration: omitting it already routed to List
(because it was the default) and supplying it produced identical behavior.
REMOVED -List from
- Get-InfisicalCertificate
- Get-InfisicalEnvironment
- Get-InfisicalFolder
- Get-InfisicalProject
- Get-InfisicalSecret
- Get-InfisicalTag
UNCHANGED
- DefaultParameterSetName = "List" remains on each cmdlet.
- All other parameters on the List set (filters, ProjectId, etc.) remain
on ParameterSetName = "List" and continue to disambiguate List vs Single.
- The Single set still requires its keying parameter (-SerialNumber,
-SecretName, -EnvironmentSlugOrId, -FolderNameOrId, -TagSlugOrId,
-ProjectId on Get-InfisicalProject) to opt into single mode.
BREAKING
- Scripts that pass -List explicitly (e.g. `Get-InfisicalSecret -List ...`)
must drop the switch. No other call shape changes.
TESTS
- PkiEndpointRegistryTests.GetInfisicalCertificate_Cmdlet_Exposes_List_Filter_Properties
no longer asserts the presence of a "List" property.
- 216/216 tests passing.
Search-InfisicalCertificate was a 1:1 duplicate of Get-InfisicalCertificate's
List parameter set after the recent filter-surface expansion (bdec5aa). Both
cmdlets exposed the same ~27 server-side filters and both hit the same
POST /api/v1/projects/{projectId}/certificates/search endpoint. Keeping two
PowerShell cmdlets for the same operation added discovery noise without
benefit.
REMOVED
- src/PSInfisicalAPI/Cmdlets/SearchInfisicalCertificateCmdlet.cs (cmdlet
source, ~140 lines).
- 'Search-InfisicalCertificate' from CmdletsToExport in the source manifest
(Module/PSInfisicalAPI/PSInfisicalAPI.psd1) and from the two generators
in build.ps1 (Write-Manifest cmdlet list + Test-ModuleImports $expectedCmds).
- <command:command> block for Search-InfisicalCertificate from the help XML
(Module/PSInfisicalAPI/en-US/PSInfisicalAPI.dll-Help.xml).
- README PKI table row for Search-InfisicalCertificate.
- "For advanced filtering ... use Search-InfisicalCertificate instead"
sentence from the Get-InfisicalCertificate Notes block (no longer true).
RETAINED (internal)
- InfisicalPkiClient.SearchCertificates, InfisicalCertificateSearchQuery,
InfisicalEndpointNames.SearchCertificates and the endpoint registry entry.
Get-InfisicalCertificate and Request-InfisicalCertificate still call them
to walk the search endpoint.
MIGRATION
# Before
Search-InfisicalCertificate -ProjectId $p -Search 'web' -Status 'active'
# After
Get-InfisicalCertificate -ProjectId $p -Search 'web' -Status 'active'
Parameter names, defaults, and paging behavior are identical.
TESTS
- 216/216 passing (one unrelated time-based test in CsrAndRequestCmdletTests
was flaky on the run; passes deterministically when invoked in isolation).
Get-InfisicalCertificatePolicy was throwing JsonSerializationException on
every list/get call:
Cannot deserialize the current JSON array (e.g. [1,2,3]) into type
'PSInfisicalAPI.Pki.InfisicalCertificatePolicySubjectDto' because the type
requires a JSON object ... Path 'certificatePolicies[0].subject', line 1,
position 207.
The API returns `subject` as an array of {type, allowed} entries (one per
DN component: CN, O, OU, C, ...), in the same shape as `sans`. The DTO
modeled it as a single object, so deserialization failed before any data
ever reached the caller.
CHANGES
- InfisicalCertificatePolicy.Subject is now InfisicalCertificatePolicySubject[]
(was a single InfisicalCertificatePolicySubject).
- DTO field switched from typed InfisicalCertificatePolicySubjectDto to
JToken SubjectRaw so we tolerate both array (current API) and object
(defensive fallback) shapes -- same pattern as SansRaw.
- Mapper gains MapSubjects(JToken) / MapSubjectObject(JToken) mirroring
MapSans / MapSanObject.
BREAKING
- The Subject property type changed from a single object to an array.
Existing consumers writing `$policy.Subject.Allowed` must update to
`$policy.Subject[0].Allowed` or iterate `$policy.Subject`. In practice no
caller was reachable because the cmdlet threw before returning.
TESTS
- 216/216 tests passing.
BREAKING CHANGES
- Connect-Infisical no longer accepts -ProjectId, -Environment, or -SecretPath.
- InfisicalConnection no longer carries ProjectId, Environment, or DefaultSecretPath.
- Every cmdlet that previously inherited those fields now requires -ProjectId
and/or -Environment as Mandatory=true. -SecretPath / -Path remain optional
and default to "/" at the client layer.
- INFISICAL_PROJECT_ID, INFISICAL_ENVIRONMENT, INFISICAL_SECRET_PATH env-var
scanning removed from Connect-Infisical.
- Resolve{ProjectId,Environment,SecretPath} helpers removed from
InfisicalCmdletBase. ResolveOrganizationId retained.
ADDED
- Get-InfisicalProject -Type <enum> filters the list by product surface
(secret-manager, cert-manager, kms, ssh, secret-scanning, pam, ai) with
IntelliSense via ValidateSet.
- Get-InfisicalProject -IncludeRoles switch maps to includeRoles=true/false
query parameter (always sent).
RATIONALE
- Implicit connection scoping caused 400 Bad Request when the active
connection's ProjectId belonged to a different product surface than the
cmdlet's target (e.g. secret-manager project id passed to /cert-manager/*).
- Explicit parameters make scope unambiguous and make scripts portable
across projects.
- The new -Type filter on Get-InfisicalProject lets callers discover the
correct project id for each subsequent CRUD invocation without needing
connection-level inheritance.
INTERNAL
- All client classes (Secrets / Folders / Environments / Tags / Projects /
Pki) now receive scoping as explicit arguments rather than reading the
InfisicalConnection object.
- Client-layer SecretPath / Path defaulting to "/" is preserved via
FirstNonEmpty(...).
- Help XML updated to remove all "session-pinned" / "active connection"
phrasing; OrderedDictionary splatting examples now include the mandatory
parameters.
- 216/216 unit tests passing.
Get-InfisicalCertificateApplication declared [Alias("Id", "ApplicationId")] on its Id parameter and Get-InfisicalCertificateApplicationEnrollment / New-InfisicalScepDynamicChallenge declared [Alias("Id", "ApplicationId")] on their ApplicationId parameter. PowerShell rejects an [Alias] entry whose value matches the parameter's own name with ParameterNameConflictsWithAlias at registration time, leaving the cmdlets unusable. Removed the self-referential alias from each.
FromEnrollment (new default) consumes an InfisicalCertificateApplicationEnrollment and auto-fills ServerUrl from scep.scepEndpointUrl, CAThumbprint from the RA certificate thumbprint, and mints a fresh dynamic challenge automatically when challengeType=dynamic and -Challenge is not supplied. FromProfile preserves the legacy projection from an InfisicalCertificateProfile but now requires -ApplicationId so the server URL is built against /scep/applications/{appId}/profiles/{profileId}/pkiclient.exe. Manual requires explicit -ServerUrl, -Challenge, and -UniqueId. Module manifest, help XML, and build.ps1 expectedCmds list updated to register the three new cmdlets. CHANGELOG updated.
Get-InfisicalCertificateApplication exposes List/ById/ByName parameter sets over /api/v1/cert-manager/applications. Get-InfisicalCertificateApplicationEnrollment returns the per-application/profile enrollment block (API/EST/ACME/SCEP) with the computed RA cert thumbprint. New-InfisicalScepDynamicChallenge wraps POST /scep/applications/{appId}/profiles/{profileId}/challenge and returns the minted challenge as a SecureString (default) or string (-AsPlainText), surfacing plan-restriction and not-configured errors as descriptive ErrorRecords.
Adds InfisicalCertificateApplication and InfisicalCertificateApplicationEnrollment models (with SCEP/EST/ACME/API sub-blocks) and DTO/mapper layer. Mapper computes the SHA-1 RA certificate thumbprint from the enrollment PEM so it can be fed directly into MDM payloads. InfisicalPkiClient gains ListCertificateApplications, GetCertificateApplication, GetCertificateApplicationByName, ListCertificateApplicationProfiles, GetCertificateApplicationEnrollment, and GenerateScepDynamicChallenge. InfisicalApiInvoker accepts an optional extraHeaders argument so callers can attach x-infisical-project-id and override Accept (used by the plain-text SCEP challenge endpoint). New endpoint names and registry entries cover /api/v1/cert-manager/applications/** and /scep/applications/**/profiles/**/challenge.
Submits an InfisicalScepMdmProfile to the local MDM Bridge WMI provider (root/cimv2/mdm/dmmap, MDM_ClientCertificateInstall_SCEP02 by default). Builds a property hashtable mirroring the CSP node set and invokes New-CimInstance via InvokeCommand.InvokeScript so no new package references are required. Guards: throws PlatformNotSupportedException off Windows; Device-scope enrollment requires an elevated session unless -SkipElevationCheck is passed; supports -WhatIf; -PassThru emits the returned CIM instance.
Writes the SyncML payload from InfisicalScepMdmProfile.ToSyncMl() to disk as UTF-8 (no BOM). Honors -WhatIf, auto-creates the target directory, and follows the project rule for -Force: if the file exists without -Force, logs a warning and returns instead of throwing. Optional -PassThru emits the resulting FileInfo.
Maps an InfisicalCertificateProfile (pipeline-bound) to an InfisicalScepMdmProfile model. ServerUrl defaults to {baseUri}/scep/{profileId}/pkiclient.exe derived from the active connection. UniqueId defaults to a sanitized slug. Challenge is accepted as SecureString and decrypted with SecureStringToGlobalAllocUnicode (zero/free on exit). KeyAlgorithm and EkuMapping are inherited from the source profile defaults when not overridden.
POCO mirrors the Windows ClientCertificateInstall/SCEP CSP node set (ServerURL, Challenge, SubjectName, SubjectAlternativeNames, EKUMapping, KeyUsage, KeyLength, KeyAlgorithm, HashAlgorithm, KeyProtection, ContainerName, ValidPeriod, ValidPeriodUnits, RetryCount, RetryDelay, TemplateName, CAThumbprint, CustomTextToShowInPrompt) plus a Scope hint (Device or User) and a UniqueId for the CSP path segment. ToSyncMl() builds an Atomic SyncBody of Replace operations and a trailing Exec on Install/Enroll using XDocument, serializes through XmlWriter with explicit settings (UTF-8 no BOM, indented, no BOM, Replace newline handling), then round-trip-validates through XmlReader before returning the string.
Issuance via Request-InfisicalCertificate -CertificateProfileId no longer throws when the API responds without a certificate body (e.g. status pending_approval / pending_validation). InfisicalPkiClient.IssueCertificateByProfile now logs a warning and returns an InfisicalSignedCertificate populated only with Status, StatusMessage, and CertificateRequestId. New Status, StatusMessage, CertificateRequestId properties on InfisicalSignedCertificate and InfisicalCertificateResult propagate the lifecycle state. The cmdlet short-circuits when CertificatePem is empty: it skips key build, install, chain install, and private-key write, scrubs PrivateKeyPem, and emits a status-only result so callers can poll or trigger approval. Whether issuance is immediate is dictated by the certificate policy bound to the profile.
New ByProfile parameter set bound by -CertificateProfileId (alias ProfileId) POSTs to /api/v1/cert-manager/certificates with the profile id, the locally generated CSR, and an attributes envelope (subject fields, ttl, notBefore, notAfter, keyUsages, extendedKeyUsages). The wrapped response is unwrapped into the existing InfisicalSignedCertificate so reuse, install, chain-completion and key-protection paths remain unchanged. Issuance that returns without a certificate (e.g. status pending_approval) raises a configuration exception that surfaces the reported status and message. Ttl/NotBefore/NotAfter/KeyUsage/ExtendedKeyUsage parameters are now shared by ByCa and ByProfile. MAML help and existing parameter-set test updated.
List parameter set gains -Kind Internal|Acme|Any. Internal (default) preserves current behavior against /api/v1/cert-manager/ca/internal. Any binds to the generic /api/v1/cert-manager/ca endpoint returning both internal and ACME CAs. Acme uses the generic endpoint and client-side filters to type=acme. ById retrieval is unchanged and still resolves against the internal CA endpoint. The existing InfisicalCertificateAuthority model already exposes a Type property to distinguish entries when -Kind Any is used. MAML help updated.
Covers GET /api/v1/cert-manager/certificate-policies (List default with optional -Limit, -Offset) and GET /api/v1/cert-manager/certificate-policies/{certificatePolicyId} (ById). New InfisicalCertificatePolicy model surfaces subject, SANs, key usages, extended key usages, algorithms, and validity. Polymorphic string-or-array fields (allowed/required/keyAlgorithm) are normalized to arrays; sans is normalized whether the API returns an object or an array. Manifest, build expected list, and MAML help updated.
Covers GET /api/v1/cert-manager/certificate-profiles (List default with optional -Limit, -Offset, -IncludeConfigs) and GET /api/v1/cert-manager/certificate-profiles/{certificateProfileId} (ById). New InfisicalCertificateProfile model surfaces ca/policy ids, slug, enrollment type, per-profile defaults (ttl, key/extended key usages with polymorphic string-or-array shapes flattened) and embedded CA/policy/apiConfig summaries. Manifest, build expected list, and MAML help updated.
Parse the JSON envelope (message/error/reqId) returned by 4xx/5xx responses and include the human-readable message in the InfisicalApiException message itself, plus new ApiErrorMessage and ApiRequestId properties. InfisicalErrorDetails and the error handler propagate the new fields so PowerShell error records and the logger surface the server-side reason instead of an opaque status line.
BREAKING: Removed Get-InfisicalProjects, Get-InfisicalEnvironments, Get-InfisicalFolders, Get-InfisicalTags, Get-InfisicalSecrets, and Get-InfisicalCertificates. Their list behavior is now the default parameter set on the singular cmdlets; supplying the identity parameter switches to single-record retrieval. No back-compat aliases.
Fix: SignCertificateBySubscriber endpoint resolved to /api/v1/pki/subscribers/{subscriberName}/sign-certificate (was /pki/pki-subscribers and /cert-manager/pki-subscribers, both 404).
Added Get-InfisicalPkiSubscriber (List/ByName), InfisicalPkiSubscriber model, DTOs, mapper, and InfisicalPkiClient.ListPkiSubscribers/GetPkiSubscriber. MAML help refreshed for all consolidated cmdlets with 2 straight-line + 1 OrderedDictionary splat examples each. README extended with extension guide. CHANGELOG updated. 230/230 tests pass.
Cmdlets added: Request-InfisicalCertificate, Get-InfisicalCertificate, Get-InfisicalCertificates. Request supports BySubscriber/ByCa parameter sets, BouncyCastle CSR generation (RSA/ECDSA/Ed25519), local-key generation, -Install/-InstallChain (chain certs routed to Root vs CertificateAuthority by self-signed status), idempotency reuse with -AllowRenewal/-RenewalThresholdDays, local chain reconstruction with -LocalChainOnly opt-out, Infisical bundle fallback when local stores are incomplete, and private-key protection modes (Exportable/LocalOnly/NonExportable/Ephemeral) via -PrivateKeyProtection plus -PersistKey/-MachineKey/-PrivateKeyPath.
Install-InfisicalCertificate fix: chain certs were previously dumped into CertificateAuthority unconditionally. They are now routed by Subject==Issuer (self-signed -> Root, otherwise -> CertificateAuthority), matching Request-InfisicalCertificate. Routing centralized in InfisicalCertificateRequestHelpers.GetChainCertificateTargetStore and a new InstallChain(IEnumerable<X509Certificate2>,...) overload.
Help: authored Module/PSInfisicalAPI/en-US/PSInfisicalAPI.dll-Help.xml covering all 39 cmdlets (synopsis, description, notes, two examples per cmdlet: one-liner + OrderedDictionary splat with preceding Get- resolvers for IDs/slugs).
Build pipeline: build.ps1 stages the help XML into bin/<culture>/ next to the DLL during publish (hard-fails if missing or has zero <command:command> entries). Test-ModuleImports now enumerates every exported cmdlet via Get-Command, cross-checks against expected names, and asserts non-empty synopsis (rejecting auto-generated cmdlet-name fallback), non-empty description, and at least one example with a non-empty <dev:code> block.
Tests: 230/230 passing (up from 190).
Normalizes Hashtable, OrderedDictionary, PSObject-wrapped, and typed generic dictionaries into IDictionary<string,string>[] before parameter binding, enabling native PowerShell @{...} and [ordered]@{...} literals against the strongly-typed -Secrets parameter on New-/Update-InfisicalSecret. Adds 8 transformation tests; 174/174 passing.
- Endpoint registry: register POST/PATCH/DELETE /api/v4/secrets/batch as preferred candidates for BulkCreate/Update/Delete; v3 raw routes retained as automatic fallback.
- DTOs: add projectId (required for v4) alongside workspaceId on the three batch request envelopes; both serialized when set, both ignored when null.
- SecretsClient: populate ProjectId in CreateBatch/UpdateBatch/DeleteBatch so v4 succeeds on first attempt.
- Cmdlets: -Secrets on New/Update-InfisicalSecret changed from Hashtable[] to IDictionary<string,string>[] for stronger typing and tab-completion; converter rewritten to accept IEnumerable<IDictionary<string,string>>. TagIds parsed from comma-separated string; nested Metadata dropped from bulk hashtable surface (still settable programmatically on bulk items).
- Tests: 166 passing (was 161). Bulk endpoints now resolve to v4 primary with v3 fallback; new tests verify projectId envelope serialization, dual-key omission, and TagIds trimming.
- Bulk parameter sets on New-/Update-/Remove-InfisicalSecret via v3/secrets/batch/raw.
- Copy-InfisicalSecret cmdlet wrapping v4/secrets/duplicate.
- InfisicalCmdletBase.Resolve{ProjectId,Environment,SecretPath,ApiVersion,OrganizationId} with verbose inheritance logging.
- All resource cmdlets refactored to use the resolution helpers.
- InfisicalBulkSecretConverter for flexible Hashtable -> DTO mapping.
- 22 new unit tests covering registry, DTOs, converter, and inheritance helpers. Total: 161 passing.