硬汉嵌入式论坛

 找回密码
 立即注册
搜索
查看: 18|回复: 0
收起左侧

新一代免费电路仿真软件QSPICE离线安装包的半自动化获取脚本

[复制链接]

2

主题

6

回帖

12

积分

新手上路

积分
12
发表于 昨天 19:19 | 显示全部楼层 |阅读模式
本帖最后由 magic_key 于 2026-10-13 19:36 编辑

  之前从youtube上偶然浏览到,老外极力推荐的QSPICE仿真软件。


  第一次安装,只能在线下载安装,对软件印象挺好,但也果断卸载掉!
  没有提供离线安装包,总会觉得,缺乏安全感。


  当时,也不了解软件作者是谁。根本不知道,原来被大量使用的传奇现象级,免费电路仿真软件LTspice,也是出自这位作者之手。
  大半生,都在做仿真软件。称其为,免费电路仿真软件仙人,应该也不过分吧?

  该软件有更快的仿真速度、更高的精度、更强的可靠性。支持使用c/c++编写数字电路模块。支持模数混合电路仿真。
  支持45度摆放元器件,经典的4个二极管组成整流桥,也在程序demo中。
  不使用弹窗设置电路参数,鼠标各种点,到处点,这种方式低效过时。直接命令行加SPICE语法提示,高效优雅、体面。
  .plot语法设置各种波形,仿真文件丢给对方,一运行即可看到想要关注的波形。
  很多功能,讲不完的,自行探索。

   一般在Google宇宙,All About Circuits论坛、eevblog论坛,是经常有甩出电路仿真文件,进行电路讨论的。
   这种讨论氛围,感觉挺好的。   

   Qorvo不提供离线安装包的理由,程序更新极其频繁。
   避免用户使用旧版本,又跑到论坛反馈那些已经被修复的问题,增加软件维护成本。

   搜索到,老外讨论的离线安装包的手动获取方式。QSPICE更新程序会下载一个几十MB的压缩包,。
   更新程序再把这个压缩包,解压出来会有100多MB,查看文件头,压缩包不是常见压缩格式。
   在不点击安装程序之前,使用Everything搜索".0"文件后缀。
   按照最新时间排序,文件名为4个小写字母加数字混合,把文件修改成".exe"就可以正常安装。

   QSPICE程序版本其实很不明确,About QSPICE里并没有明确的、具体的版本号。
   我使用安装界面Build 后面跟的一串日期和时间,作为离线安装包的文件名。

   经过实测,主程序中的Check for Updates或者文件安装路径下的‪C:\Program Files\QSPICE\Update.exe,并不会在界面上显示检测到新版本等信息,脚本无法从这里捕获到有用信息。
   使用旧版本安装程序,程序启动时,提示发现新版本的小弹窗,并不能可靠的、稳定的出现!
   这会漏掉一些新版本推送,这是一些不影响使用的小瑕疵!

   也使脚本,无法先通过检测比对新版本,再决定是否下载离线安装包。
   需要在启动软件时,有出现提示新版本更新,再执行脚本。

   总之,就折腾到这里了。


   更新程序
    屏幕截图(113).png


   软件安装界面
    屏幕截图(114).png

   powershell脚本执行
    屏幕截图(115).png


  离线安装包
    屏幕截图(116).png


    脚本内容,保存为get_qspice.ps1,编码必须使用UTF-8 with BOM


  
[C] 纯文本查看 复制代码
<#
=====================================================================
 QSPICE 离线安装包自动捕获脚本(详细注释版)
=====================================================================

 这个脚本是做什么的?
   自动完成以下流程,帮你拿到 QSPICE 的离线安装包:
     1. 启动 QSPICE 的更新下载器 Update.exe
     2. 自动点击弹窗上的 “Yes”,开始下载
     3. 监视系统临时目录 %TEMP%,捕获下载好的 .0 文件
     4. 从安装界面读取 Build 日期时间(例如 Build Oct 12 2026 17:22:16)
     5. 把 .0 文件重命名为 QSPICE_Build_YYMMDD_HHMMSS.exe 并保存

 保存编码要求(非常重要!):
   - 请将本文件保存为 UTF-8 with BOM 或 ANSI(GB2312)。
   - 如果保存为 UTF-8 无 BOM,Windows PowerShell 5.1 会中文乱码。
   - 推荐:记事本 → 另存为 → 编码选择 “UTF-8 带 BOM”。

 运行方法:
   前提条件:必须有安装QSPICE程序
   1. 以管理员身份打开 PowerShell:
        按 Win+X → 选择 “Windows PowerShell (管理员)”
   2. 允许本次会话运行脚本:
        Set-ExecutionPolicy -ExecutionPolicy Bypass -Scope Process

        (可选项)永久允许脚本运行,对当前用户永久生效,且相对安全
        Set-ExecutionPolicy RemoteSigned -Scope CurrentUser -Force
   3. 切换到脚本所在目录:
        cd C:\Users\uesr\Desktop\temp\QSPICE_TMP
   4. 执行脚本:
        .\get_qspice.ps1

 注意事项:
   - 运行前请关闭所有浏览器窗口,避免脚本误抓窗口。
   - 确保 Update.exe 路径和输出目录配置正确(见配置区)。
   - 若杀毒软件拦截,请将脚本和输出目录加入白名单。
=====================================================================
#>


# ==================== 配置区(按需修改) ====================
# QSPICE 的安装目录
$QSPICE_PATH  = "C:\Program Files\QSPICE"

# 更新程序的完整路径(把安装目录和文件名拼起来)
$UPDATE_EXE   = Join-Path $QSPICE_PATH "Update.exe"

# 系统临时目录,QSPICE 会在这里生成 .0 文件
$TEMP_DIR     = $env:TEMP

# 离线安装包保存到哪里(可自定义)
$OUTPUT_DIR   = "C:\Users\uesr\Downloads"

# 只捕获大于这个大小的 .0 文件,避免抓到无关的小文件
$MIN_SIZE_MB  = 30

# 整个脚本最长运行时间(秒),超时就放弃
$MAX_WAIT_SEC = 600
# ===========================================================


# ---------- 准备工作 1:确保输出目录存在 ----------
# 如果目录不存在就创建,-Force 表示即使已存在也不报错
if (!(Test-Path $OUTPUT_DIR)) {
    New-Item -ItemType Directory -Path $OUTPUT_DIR -Force | Out-Null
}


# ---------- 准备工作 2:加载 UI Automation 程序集 ----------
# UI Automation 是 Windows 提供的接口,可以读取窗口和控件的文字
# 我们用它来读取安装窗口里的 “Build Oct 12 2026 17:22:16” 这类信息
Add-Type -AssemblyName UIAutomationClient
Add-Type -AssemblyName UIAutomationTypes


# ============================================================
# 函数 1:Find-QspiceWindows
# 作用:查找当前所有和 QSPICE 相关的窗口(排除浏览器、编辑器)
# 返回:一个列表,每项包含 Window(窗口对象)、Title(标题)、ProcessName(进程名)
# ============================================================
function Find-QspiceWindows {

    # 获取“桌面”根元素,所有窗口都是它的子元素
    $desktop = [System.Windows.Automation.AutomationElement]::RootElement

    # 定义一个条件:只要 ControlType 是 Window 的元素
    $condition = New-Object System.Windows.Automation.PropertyCondition(
        [System.Windows.Automation.AutomationElement]::ControlTypeProperty,
        [System.Windows.Automation.ControlType]::Window)

    # 在桌面下查找所有符合条件(是窗口)的元素
    $windows = $desktop.FindAll([System.Windows.Automation.TreeScope]::Children, $condition)

    # 用来存放结果的空列表
    $result = @()

    # 逐个检查每个窗口
    foreach ($win in $windows) {

        # 读取窗口的标题
        $title = $win.GetCurrentPropertyValue([System.Windows.Automation.AutomationElement]::NameProperty)

        # 读取窗口所属进程的 ID
        $procId = $win.GetCurrentPropertyValue([System.Windows.Automation.AutomationElement]::ProcessIdProperty)

        # 如果标题为空,跳过这个窗口
        if (-not $title) { continue }

        # 排除文件属性对话框(标题里带“属性”)
        if ($title -match "属性") { continue }

        # 通过进程 ID 找到对应的进程对象
        $proc = Get-Process -Id $procId -ErrorAction SilentlyContinue
        if (-not $proc) { continue }

        # 排除浏览器和代码编辑器,避免误抓
        if ($proc.ProcessName -match "chrome|msedge|firefox|Code|devenv|notepad") { continue }

        # 只保留标题含 QSPICE 或 Installation Program 的窗口
        if ($title -match "QSPICE" -or $title -match "Installation Program for") {

            # 把结果加入列表
            $result += [PSCustomObject]@{
                Window      = $win                  # 窗口对象,后面用来读取内容或点按钮
                Title       = $title                # 窗口标题
                ProcessName = $proc.ProcessName     # 进程名
            }
        }
    }

    # 返回找到的所有窗口
    return $result
}


# ============================================================
# 函数 2:Get-AllTexts
# 作用:递归读取某个窗口内所有控件上的文字
# 参数:$Element —— 要读取的窗口元素
# 返回:字符串数组,包含窗口里所有可见文字
# ============================================================
function Get-AllTexts {
    param($Element)

    # 用来存放文字
    $texts = @()

    # 先读取传入元素自身的标题
    try {
        $name = $Element.GetCurrentPropertyValue([System.Windows.Automation.AutomationElement]::NameProperty)
        if ($name -and $name.Trim()) { $texts += $name.Trim() }
    } catch {}

    # 再查找该元素下所有后代元素(Descendants)
    try {
        $children = $Element.FindAll([System.Windows.Automation.TreeScope]::Descendants,
            [System.Windows.Automation.Condition]::TrueCondition)

        # 逐个读取每个后代元素的文字
        foreach ($child in $children) {
            try {
                $t = $child.GetCurrentPropertyValue([System.Windows.Automation.AutomationElement]::NameProperty)
                if ($t -and $t.Trim()) { $texts += $t.Trim() }
            } catch {}
        }
    } catch {}

    # 返回所有读到的文字
    return $texts
}


# ============================================================
# 函数 3:Invoke-ButtonByName
# 作用:在指定窗口里找到名称为 $ButtonName 的按钮并点击
# 参数:$Window —— 目标窗口;$ButtonName —— 按钮文字,比如 "Yes"
# 返回:$true 表示点击成功,$false 表示没找到
# ============================================================
function Invoke-ButtonByName {
    param($Window, [string]$ButtonName)

    try {
        # 定义查找条件:名字等于 $ButtonName
        $cond = New-Object System.Windows.Automation.PropertyCondition(
            [System.Windows.Automation.AutomationElement]::NameProperty, $ButtonName)

        # 在窗口下查找第一个符合条件的元素
        $btn = $Window.FindFirst([System.Windows.Automation.TreeScope]::Descendants, $cond)

        # 如果找到了按钮
        if ($btn) {
            # 获取该按钮的“调用模式”,用它来模拟点击
            $pattern = $btn.GetCurrentPattern([System.Windows.Automation.InvokePattern]::Pattern)
            $pattern.Invoke()
            return $true
        }
    } catch {}

    # 没找到或出错都返回 false
    return $false
}


# ============================================================
# 函数 4:Parse-QSPICEBuild
# 作用:从窗口文字中解析 Build 日期,生成文件名
#       例如找到 “Build Oct 12 2026 17:22:16”
#       就会返回 “QSPICE_Build_261012_172216”
# 参数:$Texts —— 字符串数组(窗口里所有文字)
# 返回:文件名(不含 .exe),或 $null 表示没找到
# ============================================================
function Parse-QSPICEBuild {
    param([string[]]$Texts)

    # 英文月份缩写 → 两位数字月份
    $monthMap = @{
        'Jan'='01'; 'Feb'='02'; 'Mar'='03'; 'Apr'='04'; 'May'='05'; 'Jun'='06'
        'Jul'='07'; 'Aug'='08'; 'Sep'='09'; 'Oct'='10'; 'Nov'='11'; 'Dec'='12'
    }

    # 遍历每一段文字,尝试匹配
    foreach ($t in $Texts) {

        # 正则解释:
        #   Build\s+            —— “Build” 后面至少一个空格
        #   ([A-Za-z]{3})\w*    —— 英文月份前三个字母(如 Oct)
        #   \s+(\d{1,2})        —— 空格后跟 1~2 位数字(日)
        #   \s+(\d{4})          —— 空格后跟 4 位数字(年)
        #   \s+(\d{1,2}):(\d{2}):(\d{2})  —— 空格后跟 时:分:秒
        $m = [regex]::Match($t, 'Build\s+([A-Za-z]{3})\w*\s+(\d{1,2})\s+(\d{4})\s+(\d{1,2}):(\d{2}):(\d{2})')

        # 如果匹配成功
        if ($m.Success) {

            # 月份英文缩写转成数字
            $mon = $monthMap[$m.Groups[1].Value.Substring(0,3)]
            if (-not $mon) { continue }

            # 取出各部分,位数不够就前面补 0
            $day  = $m.Groups[2].Value.PadLeft(2, '0')   # 日
            $year = $m.Groups[3].Value                   # 年(4 位)
            $hh   = $m.Groups[4].Value.PadLeft(2, '0')   # 时
            $mm   = $m.Groups[5].Value.PadLeft(2, '0')   # 分
            $ss   = $m.Groups[6].Value.PadLeft(2, '0')   # 秒

            # 年份只取后两位,比如 2026 → 26
            $yy   = $year.Substring(2, 2)

            # 拼成文件名并返回
            return "QSPICE_Build_${yy}${mon}${day}_${hh}${mm}${ss}"
        }
    }

    # 没找到任何匹配
    return $null
}


# ============================================================
# 主流程开始
# ============================================================

# ---- 第 1 步:启动 Update.exe ----
Write-Host " 启动更新程序: $UPDATE_EXE"

# 先检查 Update.exe 是否存在
if (-not (Test-Path $UPDATE_EXE)) {
    Write-Host "[!] 找不到更新程序: $UPDATE_EXE" -ForegroundColor Red
    exit 1
}

# 用 Start-Process 启动它(和手动双击效果一样)
Start-Process -FilePath $UPDATE_EXE


# ---- 第 2 步:进入主循环 ----
Write-Host " 开始监视 Temp 目录并等待 QSPICE 窗口..."

# 记录开始时间,用于超时判断
$startTime    = Get-Date

# 存放已捕获的 .0 文件路径(复制到输出目录后的临时名)
$capturedFile = $null

# 存放解析出的文件名(例如 QSPICE_Build_261012_172216)
$buildBase    = $null

# 记录是否已经点过 Yes,避免重复点击
$clickedYes   = $false

# 主循环:只要还没超时就一直跑
while (((Get-Date) - $startTime).TotalSeconds -lt $MAX_WAIT_SEC) {

    # ---- 2.1 查找所有 QSPICE 相关窗口 ----
    $qspiceWindows = Find-QspiceWindows

    # 逐个处理每个窗口
    foreach ($item in $qspiceWindows) {
        $win   = $item.Window
        $title = $item.Title
        $pname = $item.ProcessName

        # ---- 2.1.1 如果是第一次看到弹窗,尝试点 Yes ----
        if (-not $clickedYes) {
            if (Invoke-ButtonByName -Window $win -ButtonName "Yes") {
                Write-Host "[&#10003;] 已自动点击 Yes,开始下载。" -ForegroundColor Green
                $clickedYes = $true
                Start-Sleep -Seconds 2   # 等一下再继续
                continue
            }
        }

        # ---- 2.1.2 尝试从窗口文字里解析 Build 日期 ----
        if (-not $buildBase) {
            # 读取窗口里所有文字
            $texts = Get-AllTexts -Element $win
            # 尝试解析
            $bb = Parse-QSPICEBuild -Texts $texts
            if ($bb) {
                $buildBase = $bb
                Write-Host "[&#10003;] 从窗口 '$title' 解析到版本: $buildBase" -ForegroundColor Green
            }
        }
    }

    # ---- 2.2 尝试捕获 Temp 目录下的 .0 文件(只捕一次) ----
    if (-not $capturedFile) {

        # 列出 Temp 目录下所有扩展名为 .0 且大于阈值的大文件
        $candidates = Get-ChildItem -Path $TEMP_DIR -File -ErrorAction SilentlyContinue |
            Where-Object {
                $_.Name -match '\.0$' -and
                $_.Length -gt ($MIN_SIZE_MB * 1MB)
            }

        # 逐个检查候选文件
        foreach ($file in $candidates) {

            # 读第一次大小
            $size1 = $file.Length

            # 等 800 毫秒再读一次
            Start-Sleep -Milliseconds 800

            # 读第二次大小
            $size2 = (Get-Item $file.FullName).Length

            # 如果两次大小一样,说明文件已经写完,可以复制
            if ($size1 -eq $size2) {

                # 先复制到输出目录,用临时名(后面再改名)
                $tempDest = Join-Path $OUTPUT_DIR "QSPICE_tmp_capture.exe"
                Copy-Item -Path $file.FullName -Destination $tempDest -Force
                $capturedFile = $tempDest

                Write-Host "[&#10003;] 已捕获 .0 文件: $($file.FullName) -> $tempDest" -ForegroundColor Green
                break
            }
        }
    }

    # ---- 2.3 判断是否可以退出循环 ----
    # 只有 .0 文件已捕获 且 Build 日期已解析,才能退出
    if ($capturedFile -and $buildBase) { break }

    # 没完成就等 2 秒再试一次
    Start-Sleep -Seconds 2
}


# ============================================================
# 第 3 步:结果处理
# ============================================================

# ---- 3.1 没捕获到 .0 文件 ----
if (-not $capturedFile) {
    Write-Host "[!] 超时:未捕获到 .0 文件" -ForegroundColor Red
    exit 2
}

# ---- 3.2 没解析到 Build 日期 ----
if (-not $buildBase) {
    Write-Host "[!] 超时:未能从安装窗口解析到 Build 日期" -ForegroundColor Red
    Write-Host "已捕获的文件保留在: $capturedFile"
    Write-Host "你可以手动重命名,例如 QSPICE_Build_261012_172216.exe"
    exit 3
}

# ---- 3.3 拼出最终文件名和路径 ----
$finalName = "$buildBase.exe"
$finalPath = Join-Path $OUTPUT_DIR $finalName

# ---- 3.4 如果同名文件已存在,先删除 ----
if (Test-Path $finalPath) { Remove-Item $finalPath -Force }

# ---- 3.5 重命名临时文件为最终名字 ----
Rename-Item -Path $capturedFile -NewName $finalName -Force

# ---- 3.6 打印成功信息 ----
Write-Host "[&#10003;] 完成!离线安装包已保存为: $finalPath" -ForegroundColor Green



  
   


回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|小黑屋|Archiver|手机版|硬汉嵌入式论坛

GMT+8, 2026-10-14 06:12 , Processed in 0.126883 second(s), 25 queries .

Powered by Discuz! X3.4 Licensed

Copyright © 2001-2023, Tencent Cloud.

快速回复 返回顶部 返回列表