refactor: 部署脚本先普通权限部署,失败才提权,避免无谓UAC

deploy-plugin.bat 原为:非管理员直接 Start-Process RunAs 提权弹UAC。
但文件可写且 Roamer 可被当前用户关闭时根本不需要管理员。

重构:抽取部署主体到 deploy-body.ps1(关Roamer+等解锁+复制+校验+设写权限)。
deploy-plugin.bat 先以当前用户运行 deploy-body.ps1,成功则免UAC;
失败(如文件被管理员所有)才提权重试。
bat 用纯ASCII注释避免中文编码乱码问题。
This commit is contained in:
tian 2026-07-31 16:52:48 +08:00
parent 84f67dbd79
commit ead8a35372
2 changed files with 151 additions and 125 deletions

129
deploy-body.ps1 Normal file
View File

@ -0,0 +1,129 @@
# 插件部署主体(供 deploy-plugin.bat 调用)
# 关闭 Navisworks(Roamer)、等待文件解锁、复制 DLL 与资源、校验并设置可写权限。
$ErrorActionPreference = 'Stop'
$targetDir = [System.IO.Path]::GetFullPath('C:\ProgramData\Autodesk\Navisworks Manage 2026\plugins\TransportPlugin')
$buildDir = [System.IO.Path]::GetFullPath((Join-Path $PSScriptRoot 'bin\x64\Release'))
$deployDllNames = @(
'TransportPlugin.dll',
'geometry4Sharp.dll',
'Newtonsoft.Json.dll',
'Roy-T.AStar.dll',
'SQLite.Interop.dll',
'System.Data.SQLite.dll',
'Tomlyn.dll'
)
$deployRootFiles = @(
'TransportRibbon.xaml',
'TransportRibbon_16.png',
'TransportRibbon_32.png'
)
$stalePluginFiles = @(
'Autodesk.Navisworks.Api.dll',
'NavisworksTransport.UnitTests.dll',
'Microsoft.VisualStudio.TestPlatform.TestFramework.dll',
'Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions.dll'
)
function Test-FileUnlocked([string]$path) {
if (-not (Test-Path $path)) { return $true }
try {
$stream = [System.IO.File]::Open($path, [System.IO.FileMode]::Open, [System.IO.FileAccess]::ReadWrite, [System.IO.FileShare]::None)
$stream.Close()
return $true
} catch {
return $false
}
}
function Wait-FileUnlocked([string]$path, [datetime]$deadline) {
while (-not (Test-FileUnlocked $path)) {
if ((Get-Date) -ge $deadline) { throw ('Target file still locked: ' + $path) }
Start-Sleep -Milliseconds 500
}
}
# 关闭 Navisworks 进程以解锁 DLL
Get-Process Roamer -ErrorAction SilentlyContinue | Stop-Process -Force -ErrorAction SilentlyContinue
$deadline = (Get-Date).AddSeconds(15)
while (Get-Process Roamer -ErrorAction SilentlyContinue) {
if ((Get-Date) -ge $deadline) { throw 'Roamer.exe still running after 15 seconds.' }
Start-Sleep -Milliseconds 500
}
$keyTargetFiles = @(
(Join-Path $targetDir 'TransportPlugin.dll'),
(Join-Path $targetDir 'geometry4Sharp.dll'),
(Join-Path (Join-Path $targetDir 'resources') 'unit_cube.nwc'),
(Join-Path (Join-Path $targetDir 'resources') 'unit_cylinder.nwc')
)
foreach ($keyFile in $keyTargetFiles) { Wait-FileUnlocked $keyFile $deadline }
New-Item -ItemType Directory -Force -Path $targetDir | Out-Null
foreach ($staleFileName in $stalePluginFiles) {
$stalePath = Join-Path $targetDir $staleFileName
if (Test-Path $stalePath) { Remove-Item $stalePath -Force }
}
foreach ($dllName in $deployDllNames) {
$sourceDll = Join-Path $buildDir $dllName
if (-not (Test-Path $sourceDll)) { throw ('Deployment source file missing: ' + $sourceDll) }
Copy-Item $sourceDll $targetDir -Force
}
foreach ($rootFileName in $deployRootFiles) {
$sourceRootFile = Join-Path $buildDir $rootFileName
if (Test-Path $sourceRootFile) {
Copy-Item $sourceRootFile $targetDir -Force
foreach ($locale in @('en-US','zh-CN')) {
$localeDir = Join-Path $targetDir $locale
New-Item -ItemType Directory -Force -Path $localeDir | Out-Null
Copy-Item $sourceRootFile $localeDir -Force
}
}
}
if (Test-Path (Join-Path $buildDir 'resources')) {
New-Item -ItemType Directory -Force -Path (Join-Path $targetDir 'resources') | Out-Null
Copy-Item (Join-Path $buildDir 'resources\*') (Join-Path $targetDir 'resources') -Recurse -Force
}
# 校验复制结果
$sourceFiles = @()
foreach ($dllName in $deployDllNames) { $sourceFiles += Get-Item (Join-Path $buildDir $dllName) }
foreach ($rootFileName in $deployRootFiles) {
$sourceRootFile = Join-Path $buildDir $rootFileName
if (Test-Path $sourceRootFile) { $sourceFiles += Get-Item $sourceRootFile }
}
if (Test-Path (Join-Path $buildDir 'resources')) {
$sourceFiles += Get-ChildItem (Join-Path $buildDir 'resources') -File
}
foreach ($sourceFile in $sourceFiles) {
$targetFile = if ($sourceFile.DirectoryName -eq $buildDir) { Join-Path $targetDir $sourceFile.Name } else { Join-Path (Join-Path $targetDir 'resources') $sourceFile.Name }
if (-not (Test-Path $targetFile)) { throw ('Deployment verification failed: missing target file ' + $targetFile) }
$targetInfo = Get-Item $targetFile
if ($sourceFile.Length -ne $targetInfo.Length -or $sourceFile.LastWriteTime -ne $targetInfo.LastWriteTime) {
throw ('Deployment verification failed for ' + $sourceFile.Name + ': source=' + $sourceFile.LastWriteTime.ToString('yyyy-MM-dd HH:mm:ss.fff') + ' (' + $sourceFile.Length + '), target=' + $targetInfo.LastWriteTime.ToString('yyyy-MM-dd HH:mm:ss.fff') + ' (' + $targetInfo.Length + ')')
}
Write-Host ('Verified ' + $sourceFile.Name + ': ' + $targetInfo.LastWriteTime.ToString('yyyy-MM-dd HH:mm:ss.fff'))
if ($deployRootFiles -contains $sourceFile.Name) {
foreach ($locale in @('en-US','zh-CN')) {
$localeTargetFile = Join-Path (Join-Path $targetDir $locale) $sourceFile.Name
if (-not (Test-Path $localeTargetFile)) { throw ('Deployment verification failed: missing locale target file ' + $localeTargetFile) }
$localeTargetInfo = Get-Item $localeTargetFile
if ($sourceFile.Length -ne $localeTargetInfo.Length -or $sourceFile.LastWriteTime -ne $localeTargetInfo.LastWriteTime) {
throw ('Deployment verification failed for locale copy ' + $localeTargetFile)
}
Write-Host ('Verified ' + $locale + '\' + $sourceFile.Name + ': ' + $localeTargetInfo.LastWriteTime.ToString('yyyy-MM-dd HH:mm:ss.fff'))
}
}
}
# 设置目标文件可写权限,避免后续部署需要管理员提权(一次性管理员部署后免 UAC
foreach ($f in Get-ChildItem $targetDir -File -Recurse -ErrorAction SilentlyContinue) {
try {
$acl = Get-Acl $f.FullName
$rule = New-Object System.Security.AccessControl.FileSystemAccessRule('BUILTIN\Users','FullControl','Allow')
$acl.SetAccessRule($rule)
Set-Acl $f.FullName $acl
} catch { }
}
Write-Host 'Plugin deployed successfully!'

View File

@ -1,132 +1,29 @@
@echo off
setlocal
:: Check for admin privileges, auto-elevate if needed
net session >nul 2>&1
if %ERRORLEVEL% NEQ 0 (
PowerShell -Command "Start-Process -FilePath '%~f0' -Verb RunAs"
exit /b
REM ============================================================
REM Deploy script. Tries to deploy with current user privileges first
REM (no UAC). Only elevates and retries if normal deployment fails
REM (e.g. target files are owned by Administrator).
REM Actual deployment logic is in deploy-body.ps1.
REM ============================================================
set "SCRIPT_DIR=%~dp0"
REM First attempt: deploy with current user permissions
pwsh -NoProfile -ExecutionPolicy Bypass -File "%SCRIPT_DIR%deploy-body.ps1"
if %ERRORLEVEL% EQU 0 (
echo Deployment succeeded without elevation.
exit /b 0
)
set "TARGET_DIR=%PROGRAMDATA%\Autodesk\Navisworks Manage 2026\plugins\TransportPlugin"
set "BUILD_DIR=%~dp0bin\x64\Release"
pwsh -NoProfile -ExecutionPolicy Bypass -Command ^
"$ErrorActionPreference = 'Stop';" ^
"$targetDir = [System.IO.Path]::GetFullPath('%TARGET_DIR%');" ^
"$buildDir = [System.IO.Path]::GetFullPath('%BUILD_DIR%');" ^
"$deployDllNames = @(" ^
" 'TransportPlugin.dll'," ^
" 'geometry4Sharp.dll'," ^
" 'Newtonsoft.Json.dll'," ^
" 'Roy-T.AStar.dll'," ^
" 'SQLite.Interop.dll'," ^
" 'System.Data.SQLite.dll'," ^
" 'Tomlyn.dll'" ^
");" ^
"$deployRootFiles = @(" ^
" 'TransportRibbon.xaml'," ^
" 'TransportRibbon_16.png'," ^
" 'TransportRibbon_32.png'" ^
");" ^
"$stalePluginFiles = @(" ^
" 'Autodesk.Navisworks.Api.dll'," ^
" 'NavisworksTransport.UnitTests.dll'," ^
" 'Microsoft.VisualStudio.TestPlatform.TestFramework.dll'," ^
" 'Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions.dll'" ^
");" ^
"function Test-FileUnlocked([string]$path) {" ^
" if (-not (Test-Path $path)) { return $true }" ^
" try {" ^
" $stream = [System.IO.File]::Open($path, [System.IO.FileMode]::Open, [System.IO.FileAccess]::ReadWrite, [System.IO.FileShare]::None);" ^
" $stream.Close();" ^
" return $true;" ^
" } catch {" ^
" return $false;" ^
" }" ^
"}" ^
"function Wait-FileUnlocked([string]$path, [datetime]$deadline) {" ^
" while (-not (Test-FileUnlocked $path)) {" ^
" if ((Get-Date) -ge $deadline) { throw ('Target file still locked: ' + $path) }" ^
" Start-Sleep -Milliseconds 500;" ^
" }" ^
"}" ^
"" ^
"Get-Process Roamer -ErrorAction SilentlyContinue | Stop-Process -Force -ErrorAction SilentlyContinue;" ^
"$deadline = (Get-Date).AddSeconds(15);" ^
"while (Get-Process Roamer -ErrorAction SilentlyContinue) {" ^
" if ((Get-Date) -ge $deadline) { throw 'Roamer.exe still running after 15 seconds.' }" ^
" Start-Sleep -Milliseconds 500;" ^
"}" ^
"$keyTargetFiles = @(" ^
" (Join-Path $targetDir 'TransportPlugin.dll')," ^
" (Join-Path $targetDir 'geometry4Sharp.dll')," ^
" (Join-Path (Join-Path $targetDir 'resources') 'unit_cube.nwc')," ^
" (Join-Path (Join-Path $targetDir 'resources') 'unit_cylinder.nwc')" ^
");" ^
"foreach ($keyFile in $keyTargetFiles) { Wait-FileUnlocked $keyFile $deadline }" ^
"" ^
"New-Item -ItemType Directory -Force -Path $targetDir | Out-Null;" ^
"foreach ($staleFileName in $stalePluginFiles) {" ^
" $stalePath = Join-Path $targetDir $staleFileName;" ^
" if (Test-Path $stalePath) { Remove-Item $stalePath -Force }" ^
"}" ^
"foreach ($dllName in $deployDllNames) {" ^
" $sourceDll = Join-Path $buildDir $dllName;" ^
" if (-not (Test-Path $sourceDll)) { throw ('Deployment source file missing: ' + $sourceDll) }" ^
" Copy-Item $sourceDll $targetDir -Force;" ^
"}" ^
"foreach ($rootFileName in $deployRootFiles) {" ^
" $sourceRootFile = Join-Path $buildDir $rootFileName;" ^
" if (Test-Path $sourceRootFile) {" ^
" Copy-Item $sourceRootFile $targetDir -Force;" ^
" foreach ($locale in @('en-US','zh-CN')) {" ^
" $localeDir = Join-Path $targetDir $locale;" ^
" New-Item -ItemType Directory -Force -Path $localeDir | Out-Null;" ^
" Copy-Item $sourceRootFile $localeDir -Force;" ^
" }" ^
" }" ^
"}" ^
"if (Test-Path (Join-Path $buildDir 'resources')) {" ^
" New-Item -ItemType Directory -Force -Path (Join-Path $targetDir 'resources') | Out-Null;" ^
" Copy-Item (Join-Path $buildDir 'resources\\*') (Join-Path $targetDir 'resources') -Recurse -Force;" ^
"}" ^
"" ^
"$sourceFiles = @();" ^
"foreach ($dllName in $deployDllNames) { $sourceFiles += Get-Item (Join-Path $buildDir $dllName) }" ^
"foreach ($rootFileName in $deployRootFiles) {" ^
" $sourceRootFile = Join-Path $buildDir $rootFileName;" ^
" if (Test-Path $sourceRootFile) { $sourceFiles += Get-Item $sourceRootFile }" ^
"}" ^
"if (Test-Path (Join-Path $buildDir 'resources')) {" ^
" $sourceFiles += Get-ChildItem (Join-Path $buildDir 'resources') -File;" ^
"}" ^
"" ^
"foreach ($sourceFile in $sourceFiles) {" ^
" $targetFile = if ($sourceFile.DirectoryName -eq $buildDir) { Join-Path $targetDir $sourceFile.Name } else { Join-Path (Join-Path $targetDir 'resources') $sourceFile.Name };" ^
" if (-not (Test-Path $targetFile)) { throw ('Deployment verification failed: missing target file ' + $targetFile) }" ^
" $targetInfo = Get-Item $targetFile;" ^
" if ($sourceFile.Length -ne $targetInfo.Length -or $sourceFile.LastWriteTime -ne $targetInfo.LastWriteTime) {" ^
" throw ('Deployment verification failed for ' + $sourceFile.Name + ': source=' + $sourceFile.LastWriteTime.ToString('yyyy-MM-dd HH:mm:ss.fff') + ' (' + $sourceFile.Length + '), target=' + $targetInfo.LastWriteTime.ToString('yyyy-MM-dd HH:mm:ss.fff') + ' (' + $targetInfo.Length + ')');" ^
" }" ^
" Write-Host ('Verified ' + $sourceFile.Name + ': ' + $targetInfo.LastWriteTime.ToString('yyyy-MM-dd HH:mm:ss.fff'));" ^
" if ($deployRootFiles -contains $sourceFile.Name) {" ^
" foreach ($locale in @('en-US','zh-CN')) {" ^
" $localeTargetFile = Join-Path (Join-Path $targetDir $locale) $sourceFile.Name;" ^
" if (-not (Test-Path $localeTargetFile)) { throw ('Deployment verification failed: missing locale target file ' + $localeTargetFile) }" ^
" $localeTargetInfo = Get-Item $localeTargetFile;" ^
" if ($sourceFile.Length -ne $localeTargetInfo.Length -or $sourceFile.LastWriteTime -ne $localeTargetInfo.LastWriteTime) {" ^
" throw ('Deployment verification failed for locale copy ' + $localeTargetFile);" ^
" }" ^
" Write-Host ('Verified ' + $locale + '\\' + $sourceFile.Name + ': ' + $localeTargetInfo.LastWriteTime.ToString('yyyy-MM-dd HH:mm:ss.fff'));" ^
" }" ^
" }" ^
"}" ^
"" ^
"# 设置目标文件可写权限,避免后续部署需要管理员提权(一次性管理员部署后免 UAC" ^
"foreach (\$f in Get-ChildItem \$targetDir -File -Recurse -ErrorAction SilentlyContinue) {" ^
" try { \$acl = Get-Acl \$f.FullName; \$rule = New-Object System.Security.AccessControl.FileSystemAccessRule('BUILTIN\Users','FullControl','Allow'); \$acl.SetAccessRule(\$rule); Set-Acl \$f.FullName \$acl } catch { }" ^
"}" ^
"Write-Host 'Plugin deployed successfully!';"
REM Normal deploy failed. Elevate and retry.
echo Deployment failed with current user permissions. Retrying with administrator rights...
PowerShell -Command "Start-Process -FilePath 'pwsh.exe' -ArgumentList '-NoProfile','-ExecutionPolicy','Bypass','-File','\"%SCRIPT_DIR%deploy-body.ps1\"' -Verb RunAs -Wait"
if errorlevel 1 exit /b 1
if errorlevel 1 (
echo Deployment failed.
exit /b 1
)
echo Plugin deployed successfully.
exit /b 0