158 lines
5.4 KiB
PowerShell
158 lines
5.4 KiB
PowerShell
function Resolve-ShadowFile {
|
|
param([Parameter(Mandatory = $true)][string]$Path)
|
|
$item = Get-Item -LiteralPath $Path -ErrorAction Stop
|
|
if ($item.PSIsContainer) { throw "FILE_REQUIRED:$Path" }
|
|
return $item.FullName
|
|
}
|
|
|
|
function Resolve-ShadowDirectory {
|
|
param(
|
|
[Parameter(Mandatory = $true)][string]$Path,
|
|
[switch]$Create
|
|
)
|
|
if ($Create -and -not (Test-Path -LiteralPath $Path)) {
|
|
New-Item -ItemType Directory -Path $Path -Force | Out-Null
|
|
}
|
|
$item = Get-Item -LiteralPath $Path -ErrorAction Stop
|
|
if (-not $item.PSIsContainer) { throw "DIRECTORY_REQUIRED:$Path" }
|
|
return $item.FullName
|
|
}
|
|
|
|
function Write-ShadowJson {
|
|
param(
|
|
[Parameter(Mandatory = $true)]$Value,
|
|
[Parameter(Mandatory = $true)][string]$Path,
|
|
[int]$Depth = 12
|
|
)
|
|
$parent = Split-Path -Parent ([IO.Path]::GetFullPath($Path))
|
|
if (-not (Test-Path -LiteralPath $parent)) {
|
|
New-Item -ItemType Directory -Path $parent -Force | Out-Null
|
|
}
|
|
$Value | ConvertTo-Json -Depth $Depth | Set-Content -LiteralPath $Path `
|
|
-Encoding UTF8
|
|
}
|
|
|
|
function Read-ShadowJson {
|
|
param([Parameter(Mandatory = $true)][string]$Path)
|
|
$resolved = Resolve-ShadowFile $Path
|
|
return Get-Content -LiteralPath $resolved -Raw | ConvertFrom-Json
|
|
}
|
|
|
|
function Get-ShadowSha256 {
|
|
param([Parameter(Mandatory = $true)][string]$Path)
|
|
return (Get-FileHash -LiteralPath (Resolve-ShadowFile $Path) `
|
|
-Algorithm SHA256).Hash.ToLowerInvariant()
|
|
}
|
|
|
|
function Get-ShadowCommandLine {
|
|
param([Parameter(Mandatory = $true)][int]$ProcessId)
|
|
try {
|
|
$row = Get-CimInstance Win32_Process -Filter "ProcessId=$ProcessId" `
|
|
-ErrorAction Stop
|
|
return $row.CommandLine
|
|
}
|
|
catch { return $null }
|
|
}
|
|
|
|
function Get-ShadowListeningPorts {
|
|
param([Parameter(Mandatory = $true)][int]$ProcessId)
|
|
try {
|
|
return @(Get-NetTCPConnection -OwningProcess $ProcessId -State Listen `
|
|
-ErrorAction Stop | Sort-Object LocalPort | ForEach-Object {
|
|
[pscustomobject]@{
|
|
address = $_.LocalAddress
|
|
port = [int]$_.LocalPort
|
|
state = $_.State.ToString()
|
|
}
|
|
})
|
|
}
|
|
catch { return @() }
|
|
}
|
|
|
|
function Test-ShadowPathWithin {
|
|
param(
|
|
[Parameter(Mandatory = $true)][string]$Candidate,
|
|
[Parameter(Mandatory = $true)][string]$Directory
|
|
)
|
|
$candidatePath = [IO.Path]::GetFullPath($Candidate)
|
|
$directoryPath = [IO.Path]::GetFullPath($Directory).TrimEnd('\', '/')
|
|
if ($candidatePath.Equals($directoryPath,
|
|
[StringComparison]::OrdinalIgnoreCase)) { return $true }
|
|
$prefix = $directoryPath + [IO.Path]::DirectorySeparatorChar
|
|
return $candidatePath.StartsWith($prefix,
|
|
[StringComparison]::OrdinalIgnoreCase)
|
|
}
|
|
|
|
function Get-ShadowFreeBytes {
|
|
param([Parameter(Mandatory = $true)][string]$Path)
|
|
$root = [IO.Path]::GetPathRoot([IO.Path]::GetFullPath($Path))
|
|
return ([IO.DriveInfo]::new($root)).AvailableFreeSpace
|
|
}
|
|
|
|
function Resolve-ShadowScalarPlaceholder {
|
|
param([AllowNull()][string]$Value)
|
|
if ($null -eq $Value) { return $null }
|
|
if ($Value -match '^\$\{([A-Za-z_][A-Za-z0-9_]*):(.*)\}$') {
|
|
$environmentValue = [Environment]::GetEnvironmentVariable($matches[1])
|
|
return if ($null -eq $environmentValue) { $matches[2] } `
|
|
else { $environmentValue }
|
|
}
|
|
return $Value
|
|
}
|
|
|
|
function Read-ShadowYamlScalars {
|
|
param([Parameter(Mandatory = $true)][string]$Path)
|
|
$resolved = Resolve-ShadowFile $Path
|
|
$parents = @{}
|
|
$values = @{}
|
|
foreach ($line in Get-Content -LiteralPath $resolved) {
|
|
if ($line -match '^\s*(#.*)?$') { continue }
|
|
if ($line -notmatch '^(\s*)([A-Za-z0-9_-]+):(?:\s*(.*))?$') {
|
|
continue
|
|
}
|
|
$indent = $matches[1].Length
|
|
$key = $matches[2]
|
|
$raw = if ($null -eq $matches[3]) { '' } else { $matches[3].Trim() }
|
|
foreach ($level in @($parents.Keys | Where-Object { [int]$_ -ge $indent })) {
|
|
$parents.Remove($level)
|
|
}
|
|
if ([string]::IsNullOrWhiteSpace($raw)) {
|
|
$parents[$indent] = $key
|
|
continue
|
|
}
|
|
$segments = @($parents.Keys | Sort-Object { [int]$_ } | ForEach-Object {
|
|
$parents[$_]
|
|
}) + $key
|
|
if (($raw.StartsWith('"') -and $raw.EndsWith('"')) -or `
|
|
($raw.StartsWith("'") -and $raw.EndsWith("'"))) {
|
|
$raw = $raw.Substring(1, $raw.Length - 2)
|
|
}
|
|
$values[($segments -join '.')] = Resolve-ShadowScalarPlaceholder $raw
|
|
}
|
|
return $values
|
|
}
|
|
|
|
function Get-ShadowSqlServerJdbcIdentity {
|
|
param([Parameter(Mandatory = $true)][string]$JdbcUrl)
|
|
if ($JdbcUrl -notmatch '(?i)^jdbc:sqlserver://([^;]+)(?:;(.*))?$') {
|
|
throw 'SQLSERVER_JDBC_URL_REQUIRED'
|
|
}
|
|
$server = $matches[1].Trim()
|
|
$properties = @{}
|
|
foreach ($part in @($matches[2] -split ';')) {
|
|
if ($part -match '^\s*([^=]+)=(.*)$') {
|
|
$properties[$matches[1].Trim().ToLowerInvariant()] =
|
|
$matches[2].Trim()
|
|
}
|
|
}
|
|
$database = $properties['databasename']
|
|
if ([string]::IsNullOrWhiteSpace($database)) {
|
|
$database = $properties['database']
|
|
}
|
|
if ([string]::IsNullOrWhiteSpace($server) -or `
|
|
[string]::IsNullOrWhiteSpace($database)) {
|
|
throw 'SQLSERVER_JDBC_IDENTITY_INCOMPLETE'
|
|
}
|
|
return [pscustomobject]@{ server = $server; database = $database }
|
|
}
|