记录 MSI 安装的管理员文件阻塞 dev 部署、需提权 UAC 的问题。 根因:MSI 创建的文件对 BUILTIN\Users 只读,卸载不彻底会残留共享 DLL。 解决:一次性管理员授予目标目录及文件 Users FullControl 并解除继承保护。 同时 deploy-plugin.bat 部署成功后设置 Users 写权限,后续免 UAC。
133 lines
6.5 KiB
Batchfile
133 lines
6.5 KiB
Batchfile
@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
|
||
)
|
||
|
||
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!';"
|
||
|
||
if errorlevel 1 exit /b 1
|
||
exit /b 0
|