# 家具导出插件 - 安装包生成脚本
# 生成一个“解压后双击 安装.bat 即可安装”的简化安装包。
#
# 关键设计:
# - 设计师侧仍然只接触 .bat(双击即可),但核心逻辑用 PowerShell 执行。
# - 原因:部分机器的 AutoCAD Profile 名称可能包含 << >>,在 cmd/bat 的 for /f 子命令里
# 很容易触发重定向解析,导致 TRUSTEDPATHS 写入失败 -> 插件无法自启动(每次都要 NETLOAD)。
$ErrorActionPreference = "Stop"
function Write-TextFileUtf8NoBom {
param(
[Parameter(Mandatory = $true)][string]$Path,
[Parameter(Mandatory = $true)][string]$Content
)
# Windows 下的脚本/文本统一 CRLF,避免 cmd/某些编辑器对 LF-only 解析异常
$normalized = $Content.Replace("`r`n", "`n").Replace("`n", "`r`n")
$utf8NoBom = New-Object System.Text.UTF8Encoding($false)
[System.IO.File]::WriteAllText($Path, $normalized, $utf8NoBom)
}
function Write-TextFileUtf8Bom {
param(
[Parameter(Mandatory = $true)][string]$Path,
[Parameter(Mandatory = $true)][string]$Content
)
# Windows PowerShell 5.1 对“UTF-8 无 BOM”的脚本兼容性很差(常见表现:中文乱码甚至解析报错)。
# 设计师侧会通过 powershell.exe 执行 install.ps1 / uninstall.ps1,因此这里强制写入 UTF-8 BOM。
$normalized = $Content.Replace("`r`n", "`n").Replace("`n", "`r`n")
$utf8Bom = New-Object System.Text.UTF8Encoding($true)
[System.IO.File]::WriteAllText($Path, $normalized, $utf8Bom)
}
function Write-TextFileGbk {
param(
[Parameter(Mandatory = $true)][string]$Path,
[Parameter(Mandatory = $true)][string]$Content
)
# cmd 解析 bat 对编码/换行很敏感:使用 GBK + CRLF 兼容中文环境
$normalized = $Content.Replace("`r`n", "`n").Replace("`n", "`r`n")
$gbk = [System.Text.Encoding]::GetEncoding(936)
[System.IO.File]::WriteAllText($Path, $normalized, $gbk)
}
Write-Host "========================================" -ForegroundColor Cyan
Write-Host " 家具导出插件 - 安装包生成器" -ForegroundColor Cyan
Write-Host "========================================" -ForegroundColor Cyan
Write-Host ""
# 路径设置
$PluginName = "FurnitureExporter"
$Version = "2.6.0"
$SourceDir = Join-Path $PSScriptRoot "FurnitureExporter"
$InstallerDir = Join-Path $PSScriptRoot "installer"
$OutputDir = Join-Path $PSScriptRoot "dist"
$OutputName = "家具导出插件-v${Version}-一键安装版"
$DllSourcePath = Join-Path $SourceDir "bin\\Release\\net48\\FurnitureExporter.dll"
$PdbSourcePath = Join-Path $SourceDir "bin\\Release\\net48\\FurnitureExporter.pdb"
if (-not (Test-Path $DllSourcePath)) {
Write-Host "❌ 未找到编译好的 DLL:$DllSourcePath" -ForegroundColor Red
Write-Host "请先运行 build.ps1(或 dotnet build -c Release)编译插件" -ForegroundColor Yellow
exit 1
}
# 清理并创建安装目录
if (Test-Path $InstallerDir) {
Remove-Item -Path $InstallerDir -Recurse -Force
}
New-Item -ItemType Directory -Path $InstallerDir -Force | Out-Null
Write-Host "📦 正在准备安装包文件..." -ForegroundColor Yellow
# 1) 创建 Bundle 目录结构
$BundleDir = Join-Path $InstallerDir "${PluginName}.bundle"
$ContentsDir = Join-Path $BundleDir "Contents"
New-Item -ItemType Directory -Path $ContentsDir -Force | Out-Null
# 2) 复制 DLL / PDB
Copy-Item -Path $DllSourcePath -Destination $ContentsDir -Force
if (Test-Path $PdbSourcePath) {
Copy-Item -Path $PdbSourcePath -Destination $ContentsDir -Force
}
# 3) PackageContents.xml
$PackageXml = @"
"@
Write-TextFileUtf8NoBom -Path (Join-Path $BundleDir "PackageContents.xml") -Content $PackageXml
# 4) 安装/卸载 PowerShell 脚本(核心逻辑)
$InstallPs1 = @'
# 家具导出插件 - 一键安装脚本(PowerShell)
# 目标:
# 1) 复制 bundle 到当前用户:%AppData%\Autodesk\ApplicationPlugins
# 2) 写入 AutoCAD 2024 自启动注册表(避免每次 NETLOAD)
# 3) 追加可信路径 TRUSTEDPATHS(SECURELOAD=1 时必须,否则不自动加载)
$ErrorActionPreference = "Stop"
function Write-Step {
param([string]$Text)
Write-Host $Text -ForegroundColor Cyan
}
function Test-IsAdmin {
$identity = [Security.Principal.WindowsIdentity]::GetCurrent()
$principal = New-Object Security.Principal.WindowsPrincipal($identity)
return $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
}
function Add-TrustedPath {
param(
[AllowNull()][string]$Existing,
[Parameter(Mandatory = $true)][string]$PathToAdd
)
$normalizedAdd = $PathToAdd.Trim().TrimEnd('\')
$items = @()
if ($Existing) {
$items = $Existing.Split(';') | ForEach-Object { $_.Trim() } | Where-Object { $_ -ne "" }
}
if (-not $items -or $items.Count -eq 0) {
# 保留 AutoCAD 常见默认值,避免误删用户原本设置
$items = @("APP_PATH")
}
$exists = $false
foreach ($it in $items) {
if ($it.Trim().TrimEnd('\').Equals($normalizedAdd, [StringComparison]::OrdinalIgnoreCase)) {
$exists = $true
break
}
}
if (-not $exists) {
$items += $PathToAdd
}
# 去重(忽略大小写 + 末尾反斜杠)
$seen = New-Object "System.Collections.Generic.HashSet[string]" ([StringComparer]::OrdinalIgnoreCase)
$dedup = New-Object System.Collections.Generic.List[string]
foreach ($it in $items) {
$k = $it.Trim().TrimEnd('\')
if ($seen.Add($k)) {
$dedup.Add($it.Trim())
}
}
return ($dedup -join ";")
}
Write-Step "[0/3] 环境检查..."
if (Get-Process acad -ErrorAction SilentlyContinue) {
Write-Host ""
Write-Host "注意:检测到 AutoCAD 正在运行 (acad.exe)。" -ForegroundColor Yellow
Write-Host "为避免 DLL 被占用导致复制/删除失败,建议先完全关闭 AutoCAD 再安装。" -ForegroundColor Yellow
Write-Host ""
}
if (Test-IsAdmin) {
Write-Host ""
Write-Host "注意:你正在以管理员身份运行。" -ForegroundColor Yellow
Write-Host "本插件按“当前 Windows 用户”安装(%AppData% + HKCU 注册表)。" -ForegroundColor Yellow
Write-Host "如果不是在设计师账号下运行,安装后可能在设计师账号下不生效。" -ForegroundColor Yellow
Write-Host ""
}
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
$bundleSrc = Join-Path $scriptDir "FurnitureExporter.bundle"
# 兼容“解压覆盖旧目录”导致的套娃结构:FurnitureExporter.bundle\FurnitureExporter.bundle\...
if (-not (Test-Path (Join-Path $bundleSrc "PackageContents.xml"))) {
$nested = Join-Path $bundleSrc "FurnitureExporter.bundle"
if (Test-Path (Join-Path $nested "PackageContents.xml")) {
$bundleSrc = $nested
}
}
if (-not (Test-Path (Join-Path $bundleSrc "PackageContents.xml"))) {
Write-Host "未找到 FurnitureExporter.bundle(请先解压安装包,再运行 安装.bat)。" -ForegroundColor Red
exit 1
}
$appPlugins = Join-Path $env:APPDATA "Autodesk\ApplicationPlugins"
$bundleDst = Join-Path $appPlugins "FurnitureExporter.bundle"
$dllPath = Join-Path $bundleDst "Contents\FurnitureExporter.dll"
$trustedDir = Join-Path $bundleDst "Contents"
Write-Step "[1/3] 复制插件..."
New-Item -ItemType Directory -Path $appPlugins -Force | Out-Null
if (Test-Path $bundleDst) {
try {
Remove-Item -Path $bundleDst -Recurse -Force
}
catch {
Write-Host ""
Write-Host "删除旧版本失败:$bundleDst" -ForegroundColor Red
Write-Host "常见原因:AutoCAD 未完全关闭/文件被占用/安全软件拦截。" -ForegroundColor Yellow
Write-Host "请关闭 AutoCAD 后重试,或手动删除上述目录。" -ForegroundColor Yellow
Write-Host ""
throw
}
}
Copy-Item -Path $bundleSrc -Destination $bundleDst -Recurse -Force
if (-not (Test-Path $dllPath)) {
Write-Host "安装失败:未找到 DLL:$dllPath" -ForegroundColor Red
exit 1
}
Write-Step "[2/3] 注册自启动/可信路径..."
$acadRoot = "HKCU:\Software\Autodesk\AutoCAD\R24.3"
$curVerObj = Get-ItemProperty -Path $acadRoot -Name CurVer -ErrorAction SilentlyContinue
$acadCur = $curVerObj.CurVer
if (-not $acadCur) {
Write-Host "未检测到 AutoCAD 2024 注册表信息:$acadRoot\CurVer" -ForegroundColor Yellow
Write-Host "已完成文件复制,但未写入自启动注册。请确认已至少启动过 AutoCAD 2024 一次。" -ForegroundColor Yellow
}
else {
$acadBase = Join-Path $acadRoot $acadCur
# 1) Applications 自启动(.NET 插件标准方式)
$appKey = Join-Path $acadBase "Applications\FurnitureExporter"
New-Item -Path $appKey -Force | Out-Null
New-ItemProperty -Path $appKey -Name "DESCRIPTION" -PropertyType String -Value "家具导出插件 v$Version" -Force | Out-Null
New-ItemProperty -Path $appKey -Name "LOADCTRLS" -PropertyType DWord -Value 2 -Force | Out-Null
New-ItemProperty -Path $appKey -Name "MANAGED" -PropertyType DWord -Value 1 -Force | Out-Null
New-ItemProperty -Path $appKey -Name "LOADER" -PropertyType String -Value $dllPath -Force | Out-Null
# 2) 可信路径:只加入本插件 Contents,避免把整个 ApplicationPlugins 目录设为可信
$profilesRoot = Join-Path $acadBase "Profiles"
$profiles = Get-ChildItem -Path $profilesRoot -ErrorAction SilentlyContinue
foreach ($p in $profiles) {
$varsKey = Join-Path $p.PSPath "Variables"
if (-not (Test-Path $varsKey)) { continue }
$vars = Get-ItemProperty -Path $varsKey -ErrorAction SilentlyContinue
$existingTrusted = $null
if ($vars.PSObject.Properties.Name -contains "TRUSTEDPATHS") {
$existingTrusted = [string]$vars.TRUSTEDPATHS
}
$newTrusted = Add-TrustedPath -Existing $existingTrusted -PathToAdd $trustedDir
if ($existingTrusted -ne $newTrusted) {
New-ItemProperty -Path $varsKey -Name "TRUSTEDPATHS" -PropertyType String -Value $newTrusted -Force | Out-Null
}
# 如果 SECURELOAD=2,会导致即使加入可信路径也不加载(尽量只从 2 降到 1)
if ($vars.PSObject.Properties.Name -contains "SECURELOAD") {
if ([string]$vars.SECURELOAD -eq "2") {
New-ItemProperty -Path $varsKey -Name "SECURELOAD" -PropertyType String -Value "1" -Force | Out-Null
}
}
}
}
Write-Step "[3/3] 完成"
Write-Host ""
Write-Host "安装完成。请完全关闭 AutoCAD 后重新启动。" -ForegroundColor Green
Write-Host "启动后命令行输入:FEXPORT(单资产)或 FEXPORTPRODUCT(整套兼容)" -ForegroundColor Green
Write-Host ""
'@
Write-TextFileUtf8Bom -Path (Join-Path $InstallerDir "install.ps1") -Content $InstallPs1
$UninstallPs1 = @'
# 家具导出插件 - 一键卸载脚本(PowerShell)
# 目标:删除当前用户 ApplicationPlugins 下的 bundle,并清理自启动注册/可信路径。
$ErrorActionPreference = "Stop"
function Write-Step {
param([string]$Text)
Write-Host $Text -ForegroundColor Cyan
}
function Remove-TrustedPath {
param(
[AllowNull()][string]$Existing,
[Parameter(Mandatory = $true)][string]$PathToRemove
)
if (-not $Existing) { return $Existing }
$normalizedRemove = $PathToRemove.Trim().TrimEnd('\')
$items = $Existing.Split(';') | ForEach-Object { $_.Trim() } | Where-Object { $_ -ne "" }
$kept = New-Object System.Collections.Generic.List[string]
foreach ($it in $items) {
$k = $it.Trim().TrimEnd('\')
if (-not $k.Equals($normalizedRemove, [StringComparison]::OrdinalIgnoreCase)) {
$kept.Add($it.Trim())
}
}
return ($kept -join ";")
}
Write-Step "[0/3] 环境检查..."
if (Get-Process acad -ErrorAction SilentlyContinue) {
Write-Host ""
Write-Host "注意:检测到 AutoCAD 正在运行 (acad.exe)。" -ForegroundColor Yellow
Write-Host "如卸载失败,请先完全关闭 AutoCAD 后再重试。" -ForegroundColor Yellow
Write-Host ""
}
$appPlugins = Join-Path $env:APPDATA "Autodesk\ApplicationPlugins"
$bundleDst = Join-Path $appPlugins "FurnitureExporter.bundle"
$trustedDir = Join-Path $bundleDst "Contents"
Write-Step "[1/3] 删除插件文件..."
if (Test-Path $bundleDst) {
try {
Remove-Item -Path $bundleDst -Recurse -Force
}
catch {
Write-Host ""
Write-Host "删除失败:$bundleDst" -ForegroundColor Red
Write-Host "常见原因:AutoCAD 未完全关闭/文件被占用/安全软件拦截。" -ForegroundColor Yellow
Write-Host "你也可以手动删除该目录后,再运行 卸载.bat 清理注册表。" -ForegroundColor Yellow
Write-Host ""
throw
}
}
Write-Step "[2/3] 清理注册表..."
$acadRoot = "HKCU:\Software\Autodesk\AutoCAD\R24.3"
$curVerObj = Get-ItemProperty -Path $acadRoot -Name CurVer -ErrorAction SilentlyContinue
$acadCur = $curVerObj.CurVer
if ($acadCur) {
$acadBase = Join-Path $acadRoot $acadCur
# Applications 自启动项
$appKey = Join-Path $acadBase "Applications\FurnitureExporter"
if (Test-Path $appKey) {
Remove-Item -Path $appKey -Recurse -Force -ErrorAction SilentlyContinue
}
# TRUSTEDPATHS 移除插件 Contents(只移除我们加的这一项,避免影响其他插件)
$profilesRoot = Join-Path $acadBase "Profiles"
$profiles = Get-ChildItem -Path $profilesRoot -ErrorAction SilentlyContinue
foreach ($p in $profiles) {
$varsKey = Join-Path $p.PSPath "Variables"
if (-not (Test-Path $varsKey)) { continue }
$vars = Get-ItemProperty -Path $varsKey -ErrorAction SilentlyContinue
if (-not ($vars.PSObject.Properties.Name -contains "TRUSTEDPATHS")) { continue }
$existingTrusted = [string]$vars.TRUSTEDPATHS
$newTrusted = Remove-TrustedPath -Existing $existingTrusted -PathToRemove $trustedDir
if ($existingTrusted -ne $newTrusted) {
if ([string]::IsNullOrWhiteSpace($newTrusted)) {
Remove-ItemProperty -Path $varsKey -Name "TRUSTEDPATHS" -ErrorAction SilentlyContinue
}
else {
New-ItemProperty -Path $varsKey -Name "TRUSTEDPATHS" -PropertyType String -Value $newTrusted -Force | Out-Null
}
}
}
}
else {
Write-Host "未检测到 AutoCAD 2024 注册表信息,跳过注册表清理。" -ForegroundColor Yellow
}
Write-Step "[3/3] 完成"
Write-Host ""
Write-Host "卸载完成。请完全关闭 AutoCAD 后重新启动。" -ForegroundColor Green
Write-Host ""
'@
Write-TextFileUtf8Bom -Path (Join-Path $InstallerDir "uninstall.ps1") -Content $UninstallPs1
# 5) bat 入口(只负责启动 PowerShell 脚本)
$InstallBat = @'
@echo off
setlocal EnableExtensions
title 家具导出插件 安装程序
set "SCRIPT_DIR=%~dp0"
if not exist "%SCRIPT_DIR%install.ps1" (
echo 未找到 install.ps1
echo 请先解压安装包到当前目录后再运行。
echo.
pause
exit /b 1
)
powershell.exe -NoProfile -ExecutionPolicy Bypass -File "%SCRIPT_DIR%install.ps1"
echo.
pause
endlocal
'@
Write-TextFileGbk -Path (Join-Path $InstallerDir "安装.bat") -Content $InstallBat
$UninstallBat = @'
@echo off
setlocal EnableExtensions
title 家具导出插件 卸载程序
set "SCRIPT_DIR=%~dp0"
if not exist "%SCRIPT_DIR%uninstall.ps1" (
echo 未找到 uninstall.ps1
echo 请先解压安装包到当前目录后再运行。
echo.
pause
exit /b 1
)
powershell.exe -NoProfile -ExecutionPolicy Bypass -File "%SCRIPT_DIR%uninstall.ps1"
echo.
pause
endlocal
'@
Write-TextFileGbk -Path (Join-Path $InstallerDir "卸载.bat") -Content $UninstallBat
# 6) 使用说明
$Readme = @"
═══════════════════════════════════════════════════════════════════════
家具导出插件 v$Version - 安装指南
═══════════════════════════════════════════════════════════════════════
安装步骤(只需30秒)
───────────────────────────────────────────────────────────────────────
1) 解压安装包
2) 双击运行:安装.bat
3) 完全关闭 AutoCAD 2024 后重新启动
卸载步骤(只需10秒)
───────────────────────────────────────────────────────────────────────
1) 双击运行:卸载.bat
2) 完全关闭 AutoCAD 2024 后重新启动
使用方法
───────────────────────────────────────────────────────────────────────
打开 AutoCAD 后,命令行输入:
FEXPORT -> 单资产导出到资产中心(板材 / 组件 / 五金)
FEXPORTASSET -> 单资产导出别名
FEXPORTPRODUCT -> 整套家具兼容导出
FEXPORTBUNDLE -> 整套家具兼容导出别名
FINFO -> 查看实体信息
安装脚本做了什么(便于 IT/排查)
───────────────────────────────────────────────────────────────────────
1) 复制到:
%AppData%\Autodesk\ApplicationPlugins\FurnitureExporter.bundle
2) 注册自启动(HKCU):
HKCU\Software\Autodesk\AutoCAD\R24.3\\Applications\FurnitureExporter
3) 追加可信路径(HKCU / 每个 Profile):
TRUSTEDPATHS += ...\FurnitureExporter.bundle\Contents
常见问题
───────────────────────────────────────────────────────────────────────
Q: 提示“未知命令 FEXPORT / FEXPORTPRODUCT”
A: 1) 确认已重启 AutoCAD(关闭再打开)
2) 确认插件目录存在:
%AppData%\Autodesk\ApplicationPlugins\FurnitureExporter.bundle\Contents\FurnitureExporter.dll
3) 如果 SECURELOAD=2,脚本会将其降到 1;如仍无效请截图安装日志给开发排查
版本: v$Version
"@
Write-TextFileUtf8NoBom -Path (Join-Path $InstallerDir "使用说明.txt") -Content $Readme
# 7) 打包为 ZIP(并同时输出一个可直接双击测试的目录)
Write-Host "📦 正在生成安装包..." -ForegroundColor Yellow
New-Item -ItemType Directory -Path $OutputDir -Force | Out-Null
$ZipPath = Join-Path $OutputDir "$OutputName.zip"
Compress-Archive -Path (Join-Path $InstallerDir "*") -DestinationPath $ZipPath -Force
$StagingDir = Join-Path $OutputDir $OutputName
if (Test-Path $StagingDir) {
Remove-Item -Path $StagingDir -Recurse -Force
}
New-Item -ItemType Directory -Path $StagingDir -Force | Out-Null
Copy-Item -Path (Join-Path $InstallerDir "*") -Destination $StagingDir -Recurse -Force
Write-Host ""
Write-Host "========================================" -ForegroundColor Green
Write-Host " ✅ 安装包生成成功!" -ForegroundColor Green
Write-Host "========================================" -ForegroundColor Green
Write-Host ""
Write-Host "输出文件:" -ForegroundColor Cyan
Write-Host " - $ZipPath"
Write-Host " - $StagingDir"
Write-Host ""