diff --git a/deploy-body.ps1 b/deploy-body.ps1 new file mode 100644 index 0000000..d7c4a9b --- /dev/null +++ b/deploy-body.ps1 @@ -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!' diff --git a/deploy-plugin.bat b/deploy-plugin.bat index b4c59e1..92538ad 100644 --- a/deploy-plugin.bat +++ b/deploy-plugin.bat @@ -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