# 插件部署主体(供 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!'