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:
parent
84f67dbd79
commit
ead8a35372
129
deploy-body.ps1
Normal file
129
deploy-body.ps1
Normal 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!'
|
||||
@ -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
|
||||
|
||||
Loading…
Reference in New Issue
Block a user