#Requires -Version 7.0 # PIM Manager 3.0.70 -- Set-PimLicense.ps1, standalone (built by tools/setup/Build-PimSupportScripts.ps1; helpers inlined: _pimscriptdoc.ps1, ..\..\engine\_shared\pim-rest.ps1, pim-loopbackanswer.ps1, ..\..\engine\_shared\pim-sqlstore.ps1, ..\..\engine\_shared\pim-license.ps1, _pimsignedin.ps1) <# .SYNOPSIS Registers an issued PIM Manager licence in the environment's store: it verifies the signed licence document first, stores it as the License setting, reads it back and verifies it again; optionally stores the Invardia install key or queues its claim. .DESCRIPTION IMP-42 -- import an issued PIM Manager licence into the store (pim.Settings['License']). PIM v2 keeps the licence in SQL, not in a file. It used to be FOUND by scanning config/ for *.pimlicense / *.aitlicense -- a directory the container image excludes, so every hosted environment read "Missing" whatever had been issued. Nothing reads such a file any more: the signed document (payloadB64 + signature, byte-identical to what was issued) is stored in pim.Settings['License'], and both containers read it from there. This is the supported way to put it there. SAME SAFETY RULES AS THE OTHER STORE-WRITING SETUP SCRIPTS: * The document is VERIFIED before the store is contacted for a write (Set-PimLicense refuses a tampered or foreign-signed document -- nothing is stored). * It reads back and compares. A write that is not verified is a hope. * Any failure says so and exits non-zero; nothing is reported as installed that is not. A licence that verifies but is outside its validity window (NotYetValid / Expired / Grace) is stored -- it is the issued document -- and the status is shown in plain words so the operator knows. .PARAMETER LicensePath The issued licence file (.pimlicense / .aitlicense). Its CONTENT is imported; the file is not kept. .PARAMETER LicenseJson The issued licence document as text (JSON with payloadB64 + signature), instead of -LicensePath. .PARAMETER ConnectionString A ready connection string to the store (e.g. a local SQL Server with Integrated security). .PARAMETER SqlServer The store's server instead of -ConnectionString. An Azure SQL FQDN connects with a token -- as the admin app (-TenantId -AdminAppId with -AdminCertThumbprint or -AdminSecret) or as the signed-in person (-UseSignedInAccount: a browser sign-in, or the Invardia Support app session open in this window; a member of the SQL admin group) -- exactly like Set-PimManagerAccess.ps1. No command-line tool and no PowerShell module is needed. -WhatIf verifies the document, reads what the store holds now, prints the licence, what it replaces and every write it would make (the License setting, the install key or its claim), and writes nothing. Published as ONE self-contained file at https://invardia.com/support/pim/Set-PimLicense.ps1 (REQUIREMENTS 100.81): it runs in PowerShell 7 with nothing next to it. .EXAMPLE .\Set-PimLicense.ps1 -LicensePath .\licence.pimlicense -SqlServer .database.windows.net -TenantId -UseSignedInAccount -WhatIf Signs you in through the browser, verifies the licence and prints what it would store ("What if: ...") -- nothing is written. .EXAMPLE .\Set-PimLicense.ps1 -LicensePath .\licence.pimlicense -SqlServer .database.windows.net -TenantId -UseSignedInAccount Verifies the licence, stores it, reads it back and verifies it again. .NOTES Requirements: PowerShell: 7.0 or later (pwsh). Windows PowerShell 5.1 is not supported. Role: member of the environment's SQL admin group (grp-pim-sql-admins); no Microsoft Entra or Azure role. Hosts: login.microsoftonline.com, *.database.windows.net (port 1433) PERMISSIONS AND CHANGES (from Set-PimLicense.ps1 manifest, PIM Manager 3.0.70; full page: https://invardia.com/docs/pim/scripts/Set-PimLicense/) The person running it needs: - Member of the environment's SQL admin group (grp-pim-sql-admins), signed in through the browser or the Invardia Support app session (no command-line tool, no PowerShell module) [other, the environment's SQL database] -- The licence is kept in pim.Settings; only the SQL admin group may write it. With -UseSignedInAccount the person signs in through the browser (the recommended way). Alternatively, for automation, an Azure SQL access token (-SqlAccessToken), the admin application (-AdminAppId with -AdminCertThumbprint), or a ready -ConnectionString to a local SQL Server. What it creates, changes or grants: - update pim.Settings row 'License' (the signed licence document exactly as issued) (Environment SQL database) @ this environment's PIM Manager and engine -- Switches on the features the licence covers. The document is verified BEFORE the store is written (a tampered or foreign-signed document, a non-Pro licence or one not bound to this tenant is refused), then read back and verified again. Undo: Register the previous licence file again (the replaced licence id is printed as 'replaces:'), or remove the License setting to return to Community. - create pim.Settings row 'EditionUpgrade' (only when the environment was Community until now) (Environment SQL database) @ this environment -- Records the Community-to-Pro conversion so the update ring keeps working until the upgrade step gives the environment its Pro updater. Undo: Remove the EditionUpgrade setting. - update pim.Settings rows 'InvardiaInstallKey' and (with -InstallKeyPath) 'InstallKeyClaimState' -- only with -InstallKey / -InstallKeyPath (Environment SQL database) @ this environment -- Stores the install key Invardia issued with this installation so the engine does not claim another one. The key is never printed. Undo: Remove the two settings; the engine's install-key job then claims a key again. - update pim.Settings row 'SchedulerTriggers' (adds one 'install-key' run-now entry) -- only with -QueueInstallKeyClaim and no key given (Environment SQL database) @ this environment's engine -- So the engine claims the install key at its next run (within 5 minutes) instead of up to 6 hours later. Undo: None needed -- the entry is consumed by the next engine run. - create the result file named by -OutFile (licence id, status, features; never the install key) (Local file (only with -OutFile)) @ this computer -- For a caller that reads the outcome. Undo: Delete the file. -WhatIf: Verifies the licence document, reads what is stored now and prints the licence (id, customer, SKU, valid to, features, bound tenants, what it replaces), 'What if: Performing the operation "store the verified licence" on target "pim.Settings['License'] in /"' and 'What if: would store licence ...' (plus the install key or its claim when asked). Nothing is written. Undo: Register the previous licence file again (its id is printed as 'replaces:'), or remove the License setting to return the environment to Community. Remove InvardiaInstallKey / InstallKeyClaimState to have the engine claim a key again. .LINK https://invardia.com/docs/pim/scripts/Set-PimLicense/ #> [CmdletBinding(SupportsShouldProcess)] param( [string]$LicensePath, [string]$LicenseJson, [string]$ConnectionString, [Alias('SqlServerFqdn')][string]$SqlServer, [Alias('SqlDatabase')][string]$Database = 'PimPlatform', [string]$TenantId, [string]$AdminAppId, [string]$AdminCertThumbprint, # Operator 2026-09-23: customer environments have no certificate for the admin app on the setup host -- # a client secret is accepted instead (same parameter name as Initialize-PimMailSender / Initialize-PimTenantStore). [string]$AdminSecret, # 71.33 / 100.41: reach the store as the SIGNED-IN person (browser sign-in, a member of the SQL admin group) instead of -AdminAppId. [switch]$UseSignedInAccount, # An Azure SQL access token the caller already holds (minted for the resource https://database.windows.net/, e.g. by # the environment's own operator profile). The process's managed identity is # switched off for this run, so the store is reached as the token's principal -- never as the machine's MI. [string]$SqlAccessToken, # 2026-10-06 (with Invardia + SI): the install key Invardia issued with this installation (its bootstrap writes # `installKey` to config.json). Stored as pim.Settings['InvardiaInstallKey'] so the engine does NOT claim one -- # a claim for an environment that already has a key is refused (409). Never printed. [string]$InstallKey, # UPLINK-ENROL (PIM 99): the install key in a FILE (the MSP build's enroll step writes the one Invardia returned into its # restricted per-run directory), so the key is never a command-line or step argument. Stored exactly like -InstallKey, # and the claim state is recorded as 'claimed' (the engine's install-key job then has nothing to do). [string]$InstallKeyPath, # 2026-10-06 (install rehearsal): with no -InstallKey, queue the engine's 'install-key' job as a "Run now" trigger (the # same SchedulerTriggers entry the Manager writes), so the key is claimed at the next tick (5 min) -- not up to 6 h later, # after the first 03:00 update has already found no key. [switch]$QueueInstallKeyClaim, # 2026-10-06 (MSP rehearsal): the MSP build's licence step names the role. A licence that does not cover MSP for this # tenant is REFUSED before it is stored -- tonight a managed tenant got a single-tenant licence, and only its pull job # found out ("the licence does not include MSP"). A managed tenant runs the managing company's MSP licence. [ValidateSet('', 'Master', 'Slave')][string]$RequireMspRole = '', [string]$OutFile ) $ErrorActionPreference = 'Stop' # REQUIREMENTS 100.81: published as ONE standalone file (https://invardia.com/support/pim/Set-PimLicense.ps1). Every helper # is dot-sourced by a literal path relative to this script's folder, so Build-PimSupportScripts.ps1 can inline it -- the # built file loads nothing and needs no repository next to it. The next line tells the build to leave out the OPTIONAL # loads the engine libraries end with (files that sit next to them in the repository) and to fail on any load left. # BUILD-NO-OPTIONAL-LOADS # ===== BEGIN inlined helper: _PimScriptDoc.ps1 ===== <# .SYNOPSIS Framework 12.7 SCRIPT-DOC-1 + 12.10 item 11 -- the ONE place that knows where a PIM script's documentation page, download and checksum list live, the command form every surface shows (line 1 download, line 2+ run -- owner 2026-10-11), and the run frame every script a person runs uses (the first line "