152 lines
6.5 KiB
PowerShell
152 lines
6.5 KiB
PowerShell
[CmdletBinding()]
|
|
param(
|
|
[string]$DsViewPath = (Join-Path $env:ProgramFiles 'DSView'),
|
|
[switch]$CheckOnly
|
|
)
|
|
|
|
$ErrorActionPreference = 'Stop'
|
|
Set-StrictMode -Version 2
|
|
# Resolve source paths relative to templates, never the caller working directory.
|
|
# Standalone DSView and SETGUI use the same adapters and parsing cores.
|
|
$templateRoot = [IO.Path]::GetFullPath((Join-Path $PSScriptRoot '../..'))
|
|
$targetRoot = [IO.Path]::GetFullPath($DsViewPath)
|
|
$decoderRoot = Join-Path $targetRoot 'decoders'
|
|
|
|
# Use .NET for compatibility with PowerShell lacking Get-FileHash.
|
|
# Release both stream and hashing provider even if reading fails.
|
|
function Get-ContentHash([string]$Path) {
|
|
$algorithm = [Security.Cryptography.SHA256]::Create()
|
|
$stream = [IO.File]::OpenRead($Path)
|
|
try {
|
|
return [BitConverter]::ToString($algorithm.ComputeHash($stream))
|
|
} finally {
|
|
$stream.Dispose()
|
|
$algorithm.Dispose()
|
|
}
|
|
}
|
|
|
|
# Check all existing ancestors: a junction under decoders/common could
|
|
# redirect a local-looking update outside the chosen installation.
|
|
function Assert-PlainPath([string]$Path) {
|
|
$cursor = [IO.Path]::GetFullPath($Path)
|
|
$first = $true
|
|
while ($cursor) {
|
|
if (Test-Path -LiteralPath $cursor) {
|
|
$item = Get-Item -LiteralPath $cursor -Force
|
|
if ($item.Attributes -band [IO.FileAttributes]::ReparsePoint) {
|
|
throw "Symbolic links and junctions are not supported: $cursor"
|
|
}
|
|
if (-not $first -and -not $item.PSIsContainer) {
|
|
throw "Expected a parent directory: $cursor"
|
|
}
|
|
}
|
|
$parent = [IO.Path]::GetDirectoryName($cursor)
|
|
if ($parent -eq $cursor) { break }
|
|
$cursor = $parent
|
|
$first = $false
|
|
}
|
|
}
|
|
|
|
Assert-PlainPath $targetRoot
|
|
$exe = Join-Path $targetRoot 'DSView.exe'
|
|
if (-not (Test-Path -LiteralPath $exe -PathType Leaf)) {
|
|
throw "DSView.exe not found in '$targetRoot'. Pass -DsViewPath with the installed DSView directory."
|
|
}
|
|
if (-not (Test-Path -LiteralPath (Join-Path $decoderRoot 'common') -PathType Container)) {
|
|
throw "DSView decoders/common is missing. Select a complete DSView installation."
|
|
}
|
|
|
|
# An explicit allowlist excludes tests, bytecode and unrelated modules.
|
|
# The private common/setgui_decoders layout matches the embedded build.
|
|
$files = @()
|
|
foreach ($name in @('gate_driver_timing', 'transistor_pair', 'set_uart', 'set_can', 'pm35_uart')) {
|
|
foreach ($leaf in @('__init__.py', 'pd.py')) {
|
|
$files += [pscustomobject]@{
|
|
Source = Join-Path $PSScriptRoot "decoders/$name/$leaf"
|
|
Relative = "$name/$leaf"
|
|
}
|
|
}
|
|
}
|
|
foreach ($name in @('__init__', 'gate_timing', 'transistor_pair', 'set_uart', 'set_can', 'pm35_uart')) {
|
|
$files += [pscustomobject]@{
|
|
Source = Join-Path $templateRoot "python/logic_analyzer/decoders/$name.py"
|
|
Relative = "common/setgui_decoders/$name.py"
|
|
}
|
|
}
|
|
# Validate the entire payload and destination before changing any decoder.
|
|
foreach ($file in $files) {
|
|
if (-not (Test-Path -LiteralPath $file.Source -PathType Leaf)) {
|
|
throw "Missing templates source: $($file.Source). Initialize the templates submodule."
|
|
}
|
|
$destination = Join-Path $decoderRoot $file.Relative
|
|
Assert-PlainPath $destination
|
|
if (Test-Path -LiteralPath $destination -PathType Container) {
|
|
throw "Expected a file, found a directory: $destination"
|
|
}
|
|
if ([IO.Path]::GetFullPath($file.Source) -eq [IO.Path]::GetFullPath($destination)) {
|
|
throw 'The installation destination must differ from the templates source.'
|
|
}
|
|
}
|
|
# Read-only validation does not create backups or check elevated write access.
|
|
# Real installation performs its own write-access check before copying.
|
|
if ($CheckOnly) {
|
|
Write-Output "Validated $($files.Count) files for $targetRoot. No files changed."
|
|
return
|
|
}
|
|
# A mapped/running Windows EXE cannot be opened for writing. Never terminate DSView.
|
|
try {
|
|
$handle = [IO.File]::Open($exe, [IO.FileMode]::Open, [IO.FileAccess]::ReadWrite, [IO.FileShare]::Read)
|
|
$handle.Dispose()
|
|
} catch {
|
|
throw "Close DSView and check write permissions for '$targetRoot'. For Program Files, run PowerShell as administrator. $($_.Exception.Message)"
|
|
}
|
|
|
|
# Keep each previous version outside decoder discovery directories.
|
|
# Retain backups after success for manual recovery, not only after errors.
|
|
$backup = Join-Path $targetRoot ('set-decoder-backups/' + (Get-Date -Format 'yyyyMMdd-HHmmss') + '-' + [guid]::NewGuid().ToString('N'))
|
|
Assert-PlainPath $backup
|
|
# Back up all existing files before writing the first replacement.
|
|
foreach ($file in $files) {
|
|
$destination = Join-Path $decoderRoot $file.Relative
|
|
if (Test-Path -LiteralPath $destination -PathType Leaf) {
|
|
$saved = Join-Path $backup $file.Relative
|
|
[IO.Directory]::CreateDirectory([IO.Path]::GetDirectoryName($saved)) | Out-Null
|
|
Copy-Item -LiteralPath $destination -Destination $saved
|
|
}
|
|
}
|
|
# Track files before copying: a failed copy may leave a partial destination.
|
|
# Rollback must include that file as well as completed replacements.
|
|
$attempted = @()
|
|
try {
|
|
foreach ($file in $files) {
|
|
$destination = Join-Path $decoderRoot $file.Relative
|
|
[IO.Directory]::CreateDirectory([IO.Path]::GetDirectoryName($destination)) | Out-Null
|
|
$attempted += $file
|
|
Copy-Item -LiteralPath $file.Source -Destination $destination -Force
|
|
if ((Get-ContentHash $file.Source) -ne (Get-ContentHash $destination)) {
|
|
throw "Copy verification failed: $destination"
|
|
}
|
|
}
|
|
} catch {
|
|
$failure = $_
|
|
# Restore overwritten files and remove only new files from our allowlist.
|
|
# Never recursively delete decoder folders or remove unrelated analyzers.
|
|
foreach ($file in $attempted) {
|
|
$destination = Join-Path $decoderRoot $file.Relative
|
|
$saved = Join-Path $backup $file.Relative
|
|
try {
|
|
if (Test-Path -LiteralPath $saved -PathType Leaf) {
|
|
Copy-Item -LiteralPath $saved -Destination $destination -Force
|
|
} elseif (Test-Path -LiteralPath $destination -PathType Leaf) {
|
|
Remove-Item -LiteralPath $destination -Force
|
|
}
|
|
} catch {
|
|
Write-Warning "Could not restore $destination. Retained backup: $backup"
|
|
}
|
|
}
|
|
throw $failure
|
|
}
|
|
Write-Output "Installed: IGBT 1SP/1SD, SET UART, SET CAN, PM35 UART in $decoderRoot"
|
|
if (Test-Path -LiteralPath $backup) { Write-Output "Previous files retained in: $backup" }
|
|
Write-Output 'Restart DSView. Add UART/CAN first, then stack the corresponding SET decoder. No external Python is required.'
|