Files
templates/tools/dsview/install_decoders.ps1

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.'