7288f39f5d
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>
118 lines
4.2 KiB
PowerShell
118 lines
4.2 KiB
PowerShell
<#
|
|
.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."
|