<# .SYNOPSIS Seed the ServiceNow instance URL and integration credential into the ONE SI Key Vault. .DESCRIPTION 🔒 Operator, 2026-08-31: *"use the keyvault for the SI instance, so it is captured into one SI keyvault."* This is how those three secrets get there. The CMDB connector reads `SI-ServiceNow-Instance`, `-User` and `-Password` at RUNTIME from the vault `Get-SIVaultName` resolves. Nothing about ServiceNow is ever written to disk in this repo -- an instance URL is an infrastructure name, and this solution publishes to a public mirror. .EXAMPLE .\Set-SIServiceNowCredential.ps1 -Instance 'https://acme.service-now.com' -User 'si.integration' Prompts for the password without echoing it, then writes all three secrets. .EXAMPLE .\Set-SIServiceNowCredential.ps1 -Instance '...' -User '...' -WhatIf Shows exactly which vault and which secret names would be written, and writes nothing. .NOTES Requirements: PowerShell 7 (pwsh). Windows PowerShell 5.1 is refused at once (#Requires -Version 7.0). Role: Key Vault Secrets Officer -- on the SecurityInsight Key Vault Hosts: *.blob.core.windows.net, *.queue.core.windows.net, *.table.core.windows.net, *.vault.azure.net, 169.254.169.254, api.loganalytics.io, graph.microsoft.com, invardia.com, login.microsoftonline.com, management.azure.com, monitor.azure.com, outlook.office.com, outlook.office365.com, storage.azure.com, vault.azure.net Solution : SecurityInsight Docs : docs/V3-ARCHITECTURE.md 4.7 / 4.9 · docs/SERVICENOW-CMDB-SCHEMA.md 🔴 THE INTEGRATION USER SHOULD NOT BE AN ADMIN. It needs read on the CI classes in scope and write on the agreed objects, and nothing else. `SERVICENOW-CMDB-SCHEMA.md` §4b also requires `SecurityInsight` to exist as a `discovery_source` choice BEFORE anything can be written -- that is a change request on the customer's instance, not something this script can do. .LINK https://invardia.com/docs/si/scripts/Set-SIServiceNowCredential/ #> [CmdletBinding(SupportsShouldProcess)] param( # e.g. https://acme.service-now.com -- scheme included, no trailing path. [Parameter(Mandatory)][string]$Instance, [Parameter(Mandatory)][string]$User, # Omit to be prompted without echo. Accepting a plaintext string parameter would put the password # in the shell history and in any transcript this session is writing. [Parameter()][securestring]$Password, # Override the vault. Normally resolved from the SI config. [Parameter()][string]$VaultName ) #Requires -Version 7.0 # SecurityInsight 3.0.79 -- Set-SIServiceNowCredential.ps1, standalone (Pro edition). Built 2026-11-09T20:28:39Z from commit c1472e2f4c240c971c9559f07eef7e794b322001, source SOLUTIONS/SecurityInsight/setup/Set-SIServiceNowCredential.ps1, # by release/Build-SISupportScripts.ps1 (framework SUPPORT-DL-1). Download: Invoke-WebRequest https://invardia.com/support/si/Set-SIServiceNowCredential.ps1 -OutFile Set-SIServiceNowCredential.ps1 # It carries the 14 SecurityInsight file(s) the script loads (listed in $__siSupportFiles below, readable, each line # prefixed with "|"); at run time it writes them to a private temporary folder, checks each SHA-256, runs the original # script from there with your parameters, and removes the folder. No PowerShell modules are needed. $script:SIScriptVersion = '3.0.79' # SI #547: the version this copy reports first (stamped into its embedded setup/_shared/SI-ScriptRun.ps1 too) $__siSupportVersion = '3.0.79' $__siSupportEntry = 'setup/Set-SIServiceNowCredential.ps1' $__siSupportFiles = [ordered]@{} $__siSupportFiles['setup/Set-SIServiceNowCredential.ps1'] = @{ Sha256 = '4a997d052b6a00d4e87d4d9f05fca8cf463d4d85efe4ab921aa5b5eab9e271fe'; Text = @' |<# |.SYNOPSIS | Seed the ServiceNow instance URL and integration credential into the ONE SI Key Vault. | |.DESCRIPTION | 🔒 Operator, 2026-08-31: *"use the keyvault for the SI instance, so it is captured into one SI | keyvault."* This is how those three secrets get there. | | The CMDB connector reads `SI-ServiceNow-Instance`, `-User` and `-Password` at RUNTIME from the | vault `Get-SIVaultName` resolves. Nothing about ServiceNow is ever written to disk in this repo -- | an instance URL is an infrastructure name, and this solution publishes to a public mirror. | |.EXAMPLE | .\Set-SIServiceNowCredential.ps1 -Instance 'https://acme.service-now.com' -User 'si.integration' | Prompts for the password without echoing it, then writes all three secrets. | |.EXAMPLE | .\Set-SIServiceNowCredential.ps1 -Instance '...' -User '...' -WhatIf | Shows exactly which vault and which secret names would be written, and writes nothing. | |.NOTES | Solution : SecurityInsight | Docs : docs/V3-ARCHITECTURE.md 4.7 / 4.9 · docs/SERVICENOW-CMDB-SCHEMA.md | | 🔴 THE INTEGRATION USER SHOULD NOT BE AN ADMIN. It needs read on the CI classes in scope and write | on the agreed objects, and nothing else. `SERVICENOW-CMDB-SCHEMA.md` §4b also requires | `SecurityInsight` to exist as a `discovery_source` choice BEFORE anything can be written -- that is | a change request on the customer's instance, not something this script can do. | |.LINK | https://invardia.com/docs/si/scripts/Set-SIServiceNowCredential/ |#> |[CmdletBinding(SupportsShouldProcess)] |param( | # e.g. https://acme.service-now.com -- scheme included, no trailing path. | [Parameter(Mandatory)][string]$Instance, | [Parameter(Mandatory)][string]$User, | # Omit to be prompted without echo. Accepting a plaintext string parameter would put the password | # in the shell history and in any transcript this session is writing. | [Parameter()][securestring]$Password, | # Override the vault. Normally resolved from the SI config. | [Parameter()][string]$VaultName |) | |#Requires -Version 7.0 # after the help and the param block: before the help, it hides the help from Get-Help |# framework section 12.7 / 12.10: the documentation link first, a transcript of the run, -WhatIf writes nothing |. ([IO.Path]::Combine($PSScriptRoot, '_shared', 'SI-ScriptRun.ps1')) |$siRun = Start-SIScriptRun -Script 'Set-SIServiceNowCredential' -Quiet:($false) |try { | |$ErrorActionPreference = 'Stop' |. (Join-Path $PSScriptRoot '..\auth\Get-SIVaultName.ps1') | |$vault = Get-SIVaultName -VaultName $VaultName |Write-Host ("[INFO] vault : {0}" -f $vault) -ForegroundColor Gray |# Key Vault over native REST, no PowerShell module (#305): as the configured SI identity (SI_SPN_* / managed identity), |# an existing sign-in in this process, or a person signing in here (Azure Cloud Shell, or the browser). |if (-not (Get-Command -Name Get-SISetupAzureContext -ErrorAction SilentlyContinue)) { . ([IO.Path]::Combine($PSScriptRoot, '_shared', 'SI-SetupAzure.ps1')) } |if (-not $WhatIfPreference -and -not (Get-SISetupAzureContext)) { $null = Connect-SISetupAzure } | |# ---- validate the instance URL BEFORE writing anything ---- |# 🪤 A trailing slash or a path here becomes a 404 on every call later, and the connector reports it as |# an unreachable instance -- which sends the operator to the network team rather than to this secret. |$trimmed = $Instance.Trim().TrimEnd('/') |if ($trimmed -notmatch '^https://[A-Za-z0-9][A-Za-z0-9\.\-]*$') { | throw ("Instance '{0}' does not look like a ServiceNow base URL. Expected the form https://.service-now.com with no trailing path or slash -- a path here becomes a 404 on every call, which the connector reports as an unreachable instance." -f $Instance) |} |if ($trimmed -ne $Instance) { Write-Host ("[INFO] normalised instance to '{0}'" -f $trimmed) -ForegroundColor Gray } | |if (-not $Password) { | $Password = Read-Host -AsSecureString ("Password for ServiceNow user '{0}'" -f $User) |} |$plain = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto( | [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($Password)) |if ([string]::IsNullOrWhiteSpace($plain)) { throw 'The password is empty. Refusing to write a blank secret -- it would fail as a 401 that looks like a permissions problem.' } | |# Canonical names only. Get-SIServiceNowCredentialFromVault still READS the legacy `servicenow-pdi-*` |# names so an existing sandbox keeps working, but nothing new should be written under them. |$writes = @( | @{ Name = (Get-SIServiceNowSecretName -Part 'Instance')[0]; Value = $trimmed } | @{ Name = (Get-SIServiceNowSecretName -Part 'User')[0]; Value = $User } | @{ Name = (Get-SIServiceNowSecretName -Part 'Password')[0]; Value = $plain } |) | |foreach ($w in $writes) { | # The VALUE is never echoed -- not for the instance either, which is an infrastructure name. | if ($PSCmdlet.ShouldProcess(("{0}/{1}" -f $vault, $w.Name), 'Set Key Vault secret')) { | $sec = ConvertTo-SecureString -String ([string]$w.Value) -AsPlainText -Force | $null = Set-SIAzKeyVaultSecret -VaultName $vault -Name $w.Name -SecretValue $sec -ErrorAction Stop | Write-Host ("[OK] wrote {0}" -f $w.Name) -ForegroundColor Green | } else { | Write-Host ("[WHATIF] would write {0}" -f $w.Name) -ForegroundColor Yellow | } |} | |if ($WhatIfPreference) { return } | |# ---- read them back, because a write that reported success and stored nothing is a real shape here ---- |# 🪤 The CMDB schema doc already records that a ServiceNow `sys_dictionary` insert can return HTTP 500 |# and still SUCCEED. The inverse -- a call that returns success and did not persist -- is exactly as |# possible, and the cost of finding out at the next scheduled run is a failed sync at 3am. |Write-Host '[STEP] verifying by reading back' -ForegroundColor Cyan |. (Join-Path $PSScriptRoot '..\auth\Get-SIKvSecret.ps1') |$check = Get-SIServiceNowCredentialFromVault -VaultName $vault |if ($null -eq $check) { throw 'Wrote the secrets but read back nothing. Check the SPN has Key Vault Secrets User (read) as well as Officer (write) on this vault.' } |if ($check.Instance -ne $trimmed) { throw ("Read-back mismatch on the instance secret. Expected the value just written; got a different one -- another writer may have raced this script.") } |Write-Host ("[OK] verified: the connector will resolve this instance from vault '{0}'" -f $vault) -ForegroundColor Green |Write-Host '' |Write-Host ' Next: run the connector in plan mode -- it writes nothing and produces the match report:' -ForegroundColor White |Write-Host ' launcher\asset-cmdb\launcher.internal-vm.ps1 -Run plan' -ForegroundColor White |} finally { Stop-SIScriptRun -Run $siRun } '@ } $__siSupportFiles['setup/_shared/SI-ScriptRun.ps1'] = @{ Sha256 = 'a5c4d788d516521639611af43e1b88458216ce58bdb455885cac8e2a568afbd2'; Text = @' |#Requires -Version 5.1 |<# | EVERY SCRIPT A PERSON RUNS (framework DOCS/REQUIREMENTS.md section 12.7 SCRIPT-DOC-1 + section 12.10 SECURITY-PACK-1; SI #481 / #484). | One helper, called first by each such script: | | $siRun = Start-SIScriptRun -Script '