Add Set-HistorianCredentials.ps1 for DPAPI-encrypted credential persistence
Drops a small helper for the gated live-integration tests that need
HISTORIAN_USER + HISTORIAN_PASSWORD set in the process environment
(currently GetTagMetadataAsync_ExplicitCredentials_AgainstLocalHistorian).
Three modes:
- Default: prompts for username (default <COMPUTERNAME>\<USERNAME>) and
password (silent), saves to %USERPROFILE%\.histsdk\credentials.xml via
Export-Clixml. The SecureString inside the PSCredential is DPAPI-encrypted
and decryptable only by the same Windows user account on the same machine.
- -Load: reads the saved credential and exports HISTORIAN_USER +
HISTORIAN_PASSWORD into the current PowerShell session's environment.
- -Clear: deletes the saved credential file.
Also accepts -Path to override the storage location (e.g. for keeping
multiple credential sets side by side) and -UserName to skip the username
prompt for password-only re-saves.
Stored under the user profile, never inside the repo, so it cannot be
committed accidentally. The file format is plain Export-Clixml — no custom
encoding shenanigans.
Live-verified locally: -Load + dotnet test passes the previously-skipped
GetTagMetadataAsync_ExplicitCredentials_AgainstLocalHistorian test against
the local Historian with IntegratedSecurity=false.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,117 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Prompts for Historian credentials and persists them to an encrypted file under the
|
||||
current user's profile, or loads previously-saved credentials into the current
|
||||
session's HISTORIAN_USER and HISTORIAN_PASSWORD environment variables.
|
||||
|
||||
.DESCRIPTION
|
||||
Persistence uses Windows DPAPI via Export-Clixml. The resulting XML can only be
|
||||
decrypted by the same user account on the same machine. The file is saved
|
||||
outside the repository (default: $env:USERPROFILE\.histsdk\credentials.xml) so
|
||||
it cannot be accidentally committed.
|
||||
|
||||
The integration tests in this repo read HISTORIAN_USER and HISTORIAN_PASSWORD
|
||||
from the process environment. Run this script with -Load before launching
|
||||
`dotnet test` to inject the saved credentials into the current session.
|
||||
|
||||
.PARAMETER Load
|
||||
Read previously-saved credentials and set $env:HISTORIAN_USER + $env:HISTORIAN_PASSWORD
|
||||
in the current PowerShell session. The variables persist for the lifetime of
|
||||
this session only - re-run with -Load in a new shell.
|
||||
|
||||
.PARAMETER Clear
|
||||
Delete the saved credentials file. Subsequent -Load calls will fail until
|
||||
credentials are re-saved.
|
||||
|
||||
.PARAMETER Path
|
||||
Override the default storage path. Useful for keeping multiple credential sets
|
||||
side-by-side (e.g. local-vs-remote).
|
||||
|
||||
.PARAMETER UserName
|
||||
Skip the username prompt and use the supplied value. Password is still prompted
|
||||
interactively. Convenient for scripted re-saves where only the password rotates.
|
||||
|
||||
.EXAMPLE
|
||||
PS> .\scripts\Set-HistorianCredentials.ps1
|
||||
Prompts for username + password, saves both to %USERPROFILE%\.histsdk\credentials.xml.
|
||||
|
||||
.EXAMPLE
|
||||
PS> .\scripts\Set-HistorianCredentials.ps1 -Load
|
||||
Loads the saved credentials into HISTORIAN_USER and HISTORIAN_PASSWORD for this session.
|
||||
|
||||
.EXAMPLE
|
||||
PS> .\scripts\Set-HistorianCredentials.ps1 -Clear
|
||||
Deletes the saved credentials file.
|
||||
#>
|
||||
[CmdletBinding(DefaultParameterSetName = 'Save')]
|
||||
param(
|
||||
[Parameter(ParameterSetName = 'Load')]
|
||||
[switch]$Load,
|
||||
|
||||
[Parameter(ParameterSetName = 'Clear')]
|
||||
[switch]$Clear,
|
||||
|
||||
[string]$Path = (Join-Path $env:USERPROFILE '.histsdk\credentials.xml'),
|
||||
|
||||
[Parameter(ParameterSetName = 'Save')]
|
||||
[string]$UserName
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
function ConvertTo-PlainText {
|
||||
param([Parameter(Mandatory)][securestring]$SecureString)
|
||||
$bstr = [Runtime.InteropServices.Marshal]::SecureStringToBSTR($SecureString)
|
||||
try { [Runtime.InteropServices.Marshal]::PtrToStringBSTR($bstr) }
|
||||
finally { [Runtime.InteropServices.Marshal]::ZeroFreeBSTR($bstr) }
|
||||
}
|
||||
|
||||
if ($Clear) {
|
||||
if (Test-Path $Path) {
|
||||
Remove-Item -LiteralPath $Path -Force
|
||||
Write-Host "Deleted $Path"
|
||||
} else {
|
||||
Write-Host "Nothing to clear (file does not exist): $Path"
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if ($Load) {
|
||||
if (-not (Test-Path $Path)) {
|
||||
throw "No saved credentials at $Path. Run this script without -Load to save them first."
|
||||
}
|
||||
$cred = Import-Clixml -LiteralPath $Path
|
||||
if ($cred -isnot [System.Management.Automation.PSCredential]) {
|
||||
throw "File at $Path is not a PSCredential - re-save with this script."
|
||||
}
|
||||
$env:HISTORIAN_USER = $cred.UserName
|
||||
$env:HISTORIAN_PASSWORD = ConvertTo-PlainText $cred.Password
|
||||
Write-Host "Loaded credentials for '$($cred.UserName)' from $Path."
|
||||
Write-Host "HISTORIAN_USER and HISTORIAN_PASSWORD are set for this PowerShell session."
|
||||
return
|
||||
}
|
||||
|
||||
# Save mode (default).
|
||||
$dir = Split-Path -Parent $Path
|
||||
if (-not (Test-Path $dir)) {
|
||||
New-Item -ItemType Directory -Force -Path $dir | Out-Null
|
||||
}
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($UserName)) {
|
||||
$suggested = "$env:COMPUTERNAME\$env:USERNAME"
|
||||
$UserName = Read-Host "Historian user [$suggested]"
|
||||
if ([string]::IsNullOrWhiteSpace($UserName)) {
|
||||
$UserName = $suggested
|
||||
}
|
||||
}
|
||||
|
||||
$securePassword = Read-Host "Historian password for '$UserName'" -AsSecureString
|
||||
$cred = New-Object System.Management.Automation.PSCredential($UserName, $securePassword)
|
||||
$cred | Export-Clixml -LiteralPath $Path
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "Saved credentials for '$UserName' to $Path"
|
||||
Write-Host "(DPAPI-encrypted; decryptable only by '$env:USERNAME' on '$env:COMPUTERNAME'.)"
|
||||
Write-Host ""
|
||||
Write-Host "Run '.\scripts\Set-HistorianCredentials.ps1 -Load' in any new session to inject"
|
||||
Write-Host "the credentials into HISTORIAN_USER and HISTORIAN_PASSWORD."
|
||||
Reference in New Issue
Block a user