fix: add AssemblyResolve handler for reflection diagnostics
Previous version failed GetExportedTypes() because Assembly.Load(bytes) couldn't resolve Disco.Services.dll from the PowerShell process. Now uses LoadFrom() with an AssemblyResolve handler that probes both the build output and Disco bin directories. Also adds ReflectionTypeLoad exception detail logging and proper handler cleanup in finally block.
This commit is contained in:
1 parent
7cf07b50d2
commit
df6671d729
1 file changed
+1
-479
+1
-479
@@ -1,479 +1 @@
|
|||||||
# Build-Plugin.ps1
|
IyBCdWlsZC1QbHVnaW4ucHMxCiMgQnVpbGRzIGFuZCBwYWNrYWdlcyB0aGUgQUQgQ29tcGFyZSBwbHVnaW4gZm9yIERpc2NvIElDVCBpbXBvcnQKIwojIFVzYWdlOgojICAgLlxCdWlsZC1QbHVnaW4ucHMxIC1EaXNjb0JpblBhdGggIkM6XFByb2dyYW0gRmlsZXNcRGlzY29cV2ViQXBwXGJpbiIKCnBhcmFtKAogICAgW1BhcmFtZXRlcihNYW5kYXRvcnk9JHRydWUpXQogICAgW3N0cmluZ10kRGlzY29CaW5QYXRoLAoKICAgIFtzdHJpbmddJENvbmZpZ3VyYXRpb24gPSAiUmVsZWFzZSIKKQoKJEVycm9yQWN0aW9uUHJlZmVyZW5jZSA9ICJTdG9wIgokUGx1Z2luRGlyID0gJFBTU2NyaXB0Um9vdAokUGx1Z2luTmFtZSA9ICJEaXNjby5QbHVnaW5zLkFEQ29tcGFyZSIKCldyaXRlLUhvc3QgIj09PSBBRCBDb21wYXJlIFBsdWdpbiBCdWlsZGVyID09PSIgLUZvcmVncm91bmRDb2xvciBDeWFuCldyaXRlLUhvc3QgIkRpc2NvIGJpbiBwYXRoOiAkRGlzY29CaW5QYXRoIiAtRm9yZWdyb3VuZENvbG9yIEdyYXkKCiMgLS0tIFZhbGlkYXRlIC0tLQppZiAoLW5vdCAoVGVzdC1QYXRoICREaXNjb0JpblBhdGgpKSB7CiAgICBXcml0ZS1FcnJvciAiRGlzY28gYmluIHBhdGggbm90IGZvdW5kOiAkRGlzY29CaW5QYXRoIgogICAgZXhpdCAxCn0KCiRyZXF1aXJlZERsbHMgPSBAKCJEaXNjby5TZXJ2aWNlcy5kbGwiLCAiRGlzY28uTW9kZWxzLmRsbCIsICJEaXNjby5EYXRhLmRsbCIpCmZvcmVhY2ggKCRkbGwgaW4gJHJlcXVpcmVkRGxscykgewogICAgJGRsbFBhdGggPSBKb2luLVBhdGggJERpc2NvQmluUGF0aCAkZGxsCiAgICBpZiAoLW5vdCAoVGVzdC1QYXRoICRkbGxQYXRoKSkgewogICAgICAgIFdyaXRlLUVycm9yICJSZXF1aXJlZCBhc3NlbWJseSBub3QgZm91bmQ6ICRkbGxQYXRoIgogICAgICAgIGV4aXQgMQogICAgfQp9CldyaXRlLUhvc3QgIkFsbCByZXF1aXJlZCBEaXNjbyBhc3NlbWJsaWVzIGZvdW5kIiAtRm9yZWdyb3VuZENvbG9yIEdyZWVuCgojIC0tLSBTaG93IERpc2NvIEFzc2VtYmx5IFZlcnNpb25zIC0tLQpXcml0ZS1Ib3N0ICJgbi0tLSBEaXNjbyBBc3NlbWJseSBWZXJzaW9ucyAtLS0iIC1Gb3JlZ3JvdW5kQ29sb3IgRGFya0dyYXkKZm9yZWFjaCAoJGRsbCBpbiAkcmVxdWlyZWREbGxzKSB7CiAgICAkZGxsUGF0aCA9IEpvaW4tUGF0aCAkRGlzY29CaW5QYXRoICRkbGwKICAgIHRyeSB7CiAgICAgICAgJGFzbU5hbWUgPSBbU3lzdGVtLlJlZmxlY3Rpb24uQXNzZW1ibHlOYW1lXTo6R2V0QXNzZW1ibHlOYW1lKCRkbGxQYXRoKQogICAgICAgIFdyaXRlLUhvc3QgIiAgJGRsbCA6IHYkKCRhc21OYW1lLlZlcnNpb24pIiAtRm9yZWdyb3VuZENvbG9yIEdyYXkKICAgIH0gY2F0Y2ggewogICAgICAgIFdyaXRlLVdhcm5pbmcgIiAgJGRsbCA6IGNvdWxkIG5vdCByZWFkIHZlcnNpb24gLSAkKCRfLkV4Y2VwdGlvbi5NZXNzYWdlKSIKICAgIH0KfQoKIyAtLS0gRmluZCBNU0J1aWxkIC0tLQokbXNidWlsZCA9ICRudWxsCiR2c1doZXJlID0gIiR7ZW52OlByb2dyYW1GaWxlcyh4ODYpfVxNaWNyb3NvZnQgVmlzdWFsIFN0dWRpb1xJbnN0YWxsZXJcdnN3aGVyZS5leGUiCmlmIChUZXN0LVBhdGggJHZzV2hlcmUpIHsKICAgICRtc2J1aWxkID0gJiAkdnNXaGVyZSAtbGF0ZXN0IC1yZXF1aXJlcyBNaWNyb3NvZnQuQ29tcG9uZW50Lk1TQnVpbGQgLWZpbmQgIk1TQnVpbGRcKipcQmluXE1TQnVpbGQuZXhlIiAyPiRudWxsIHwgU2VsZWN0LU9iamVjdCAtRmlyc3QgMQp9CmlmICgtbm90ICRtc2J1aWxkIC1vciAtbm90IChUZXN0LVBhdGggJG1zYnVpbGQpKSB7CiAgICAkbXNidWlsZCA9ICJDOlxXaW5kb3dzXE1pY3Jvc29mdC5ORVRcRnJhbWV3b3JrNjRcdjQuMC4zMDMxOVxNU0J1aWxkLmV4ZSIKfQppZiAoLW5vdCAoVGVzdC1QYXRoICRtc2J1aWxkKSkgewogICAgV3JpdGUtRXJyb3IgIk1TQnVpbGQgbm90IGZvdW5kLiIKICAgIGV4aXQgMQp9CldyaXRlLUhvc3QgIlVzaW5nIE1TQnVpbGQ6ICRtc2J1aWxkIiAtRm9yZWdyb3VuZENvbG9yIEdyYXkKCiMgLS0tIFJlc29sdmUgYWxsIEhpbnRQYXRocyB0byBEaXNjbyBiaW4gLS0tCiRjc3Byb2ogPSBKb2luLVBhdGggJFBsdWdpbkRpciAiJFBsdWdpbk5hbWUuY3Nwcm9qIgokY3Nwcm9qQ29udGVudCA9IEdldC1Db250ZW50ICRjc3Byb2ogLVJhdwokY3Nwcm9qQ29udGVudCA9IFtyZWdleF06OlJlcGxhY2UoJGNzcHJvakNvbnRlbnQsICc8SGludFBhdGg+W148XStcXChbXlxcPF0rXC5kbGwpPC9IaW50UGF0aD4nLCAiPEhpbnRQYXRoPiREaXNjb0JpblBhdGhcYCQxPC9IaW50UGF0aD4iKQoKJHRlbXBDc3Byb2ogPSBKb2luLVBhdGggJFBsdWdpbkRpciAiJFBsdWdpbk5hbWUuYnVpbGQuY3Nwcm9qIgokY3Nwcm9qQ29udGVudCB8IFNldC1Db250ZW50ICR0ZW1wQ3Nwcm9qIC1FbmNvZGluZyBVVEY4CldyaXRlLUhvc3QgIkFzc2VtYmx5IHJlZmVyZW5jZXMgcmVzb2x2ZWQgdG8gJERpc2NvQmluUGF0aCIgLUZvcmVncm91bmRDb2xvciBHcmVlbgoKIyAtLS0gQnVpbGQgLS0tCldyaXRlLUhvc3QgImBuQnVpbGRpbmcgJFBsdWdpbk5hbWUgKCRDb25maWd1cmF0aW9uKS4uLiIgLUZvcmVncm91bmRDb2xvciBZZWxsb3cKJGJ1aWxkT3V0cHV0ID0gSm9pbi1QYXRoICRQbHVnaW5EaXIgImJpblwkQ29uZmlndXJhdGlvbiIKCiYgJG1zYnVpbGQgJHRlbXBDc3Byb2ogL3A6Q29uZmlndXJhdGlvbj0kQ29uZmlndXJhdGlvbiAvcDpPdXRwdXRQYXRoPSRidWlsZE91dHB1dCAvdDpCdWlsZCAvdjptaW5pbWFsCgppZiAoJExBU1RFWElUQ09ERSAtbmUgMCkgewogICAgUmVtb3ZlLUl0ZW0gJHRlbXBDc3Byb2ogLUVycm9yQWN0aW9uIFNpbGVudGx5Q29udGludWUKICAgIFdyaXRlLUVycm9yICJCdWlsZCBmYWlsZWQhIgogICAgZXhpdCAxCn0KUmVtb3ZlLUl0ZW0gJHRlbXBDc3Byb2ogLUVycm9yQWN0aW9uIFNpbGVudGx5Q29udGludWUKCiRwbHVnaW5EbGwgPSBKb2luLVBhdGggJGJ1aWxkT3V0cHV0ICIkUGx1Z2luTmFtZS5kbGwiCmlmICgtbm90IChUZXN0LVBhdGggJHBsdWdpbkRsbCkpIHsKICAgIFdyaXRlLUVycm9yICJCdWlsZCBvdXRwdXQgbm90IGZvdW5kOiAkcGx1Z2luRGxsIgogICAgZXhpdCAxCn0KV3JpdGUtSG9zdCAiQnVpbGQgc3VjY2Vzc2Z1bDogJHBsdWdpbkRsbCIgLUZvcmVncm91bmRDb2xvciBHcmVlbgoKIyAtLS0gQ29weSBEZXBlbmRlbmNpZXMgZm9yIFJlZmxlY3Rpb24gYW5kIE1hbmlmZXN0R2VuZXJhdG9yIC0tLQpmb3JlYWNoICgkZGxsIGluICRyZXF1aXJlZERsbHMpIHsKICAgIENvcHktSXRlbSAoSm9pbi1QYXRoICREaXNjb0JpblBhdGggJGRsbCkgJGJ1aWxkT3V0cHV0IC1Gb3JjZSAtRXJyb3JBY3Rpb24gU2lsZW50bHlDb250aW51ZQp9CiRleHRyYURsbHMgPSBAKCJFbnRpdHlGcmFtZXdvcmsuZGxsIiwgIlN5c3RlbS5XZWIuTXZjLmRsbCIsICJTeXN0ZW0uV2ViLldlYlBhZ2VzLmRsbCIsICJTeXN0ZW0uV2ViLlJhem9yLmRsbCIsICJSYXpvckdlbmVyYXRvci5NdmMuZGxsIiwgIk5ld3RvbnNvZnQuSnNvbi5kbGwiKQpmb3JlYWNoICgkZGVwIGluICRleHRyYURsbHMpIHsKICAgICRkZXBQYXRoID0gSm9pbi1QYXRoICREaXNjb0JpblBhdGggJGRlcAogICAgaWYgKFRlc3QtUGF0aCAkZGVwUGF0aCkgeyBDb3B5LUl0ZW0gJGRlcFBhdGggJGJ1aWxkT3V0cHV0IC1Gb3JjZSAtRXJyb3JBY3Rpb24gU2lsZW50bHlDb250aW51ZSB9Cn0KCiMgLS0tIFJlZmxlY3Rpb24tQmFzZWQgRmVhdHVyZSBEaWFnbm9zdGljcyAtLS0KV3JpdGUtSG9zdCALine truncated
|
||||||
# Builds and packages the AD Compare plugin for Disco ICT import
|
|
||||||
#
|
|
||||||
# Usage:
|
|
||||||
# .\Build-Plugin.ps1 -DiscoBinPath "C:\Program Files\Disco\WebApp\bin"
|
|
||||||
|
|
||||||
param(
|
|
||||||
[Parameter(Mandatory=$true)]
|
|
||||||
[string]$DiscoBinPath,
|
|
||||||
|
|
||||||
[string]$Configuration = "Release"
|
|
||||||
)
|
|
||||||
|
|
||||||
$ErrorActionPreference = "Stop"
|
|
||||||
$PluginDir = $PSScriptRoot
|
|
||||||
$PluginName = "Disco.Plugins.ADCompare"
|
|
||||||
|
|
||||||
Write-Host "=== AD Compare Plugin Builder ===" -ForegroundColor Cyan
|
|
||||||
Write-Host "Disco bin path: $DiscoBinPath" -ForegroundColor Gray
|
|
||||||
|
|
||||||
# --- Validate ---
|
|
||||||
if (-not (Test-Path $DiscoBinPath)) {
|
|
||||||
Write-Error "Disco bin path not found: $DiscoBinPath"
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
$requiredDlls = @("Disco.Services.dll", "Disco.Models.dll", "Disco.Data.dll")
|
|
||||||
foreach ($dll in $requiredDlls) {
|
|
||||||
$dllPath = Join-Path $DiscoBinPath $dll
|
|
||||||
if (-not (Test-Path $dllPath)) {
|
|
||||||
Write-Error "Required assembly not found: $dllPath"
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Write-Host "All required Disco assemblies found" -ForegroundColor Green
|
|
||||||
|
|
||||||
# --- Show Disco Assembly Versions ---
|
|
||||||
Write-Host "`n--- Disco Assembly Versions ---" -ForegroundColor DarkGray
|
|
||||||
foreach ($dll in $requiredDlls) {
|
|
||||||
$dllPath = Join-Path $DiscoBinPath $dll
|
|
||||||
try {
|
|
||||||
$asmName = [System.Reflection.AssemblyName]::GetAssemblyName($dllPath)
|
|
||||||
Write-Host " $dll : v$($asmName.Version)" -ForegroundColor Gray
|
|
||||||
} catch {
|
|
||||||
Write-Warning " $dll : could not read version - $($_.Exception.Message)"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# --- Find MSBuild ---
|
|
||||||
$msbuild = $null
|
|
||||||
$vsWhere = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe"
|
|
||||||
if (Test-Path $vsWhere) {
|
|
||||||
$msbuild = & $vsWhere -latest -requires Microsoft.Component.MSBuild -find "MSBuild\**\Bin\MSBuild.exe" 2>$null | Select-Object -First 1
|
|
||||||
}
|
|
||||||
if (-not $msbuild -or -not (Test-Path $msbuild)) {
|
|
||||||
$msbuild = "C:\Windows\Microsoft.NET\Framework64\v4.0.30319\MSBuild.exe"
|
|
||||||
}
|
|
||||||
if (-not (Test-Path $msbuild)) {
|
|
||||||
Write-Error "MSBuild not found."
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
Write-Host "Using MSBuild: $msbuild" -ForegroundColor Gray
|
|
||||||
|
|
||||||
# --- Resolve all HintPaths to Disco bin ---
|
|
||||||
$csproj = Join-Path $PluginDir "$PluginName.csproj"
|
|
||||||
$csprojContent = Get-Content $csproj -Raw
|
|
||||||
$csprojContent = [regex]::Replace($csprojContent, '<HintPath>[^<]+\\([^\\<]+\.dll)</HintPath>', "<HintPath>$DiscoBinPath\`$1</HintPath>")
|
|
||||||
|
|
||||||
$tempCsproj = Join-Path $PluginDir "$PluginName.build.csproj"
|
|
||||||
$csprojContent | Set-Content $tempCsproj -Encoding UTF8
|
|
||||||
Write-Host "Assembly references resolved to $DiscoBinPath" -ForegroundColor Green
|
|
||||||
|
|
||||||
# --- Build ---
|
|
||||||
Write-Host "`nBuilding $PluginName ($Configuration)..." -ForegroundColor Yellow
|
|
||||||
$buildOutput = Join-Path $PluginDir "bin\$Configuration"
|
|
||||||
|
|
||||||
& $msbuild $tempCsproj /p:Configuration=$Configuration /p:OutputPath=$buildOutput /t:Build /v:minimal
|
|
||||||
|
|
||||||
if ($LASTEXITCODE -ne 0) {
|
|
||||||
Remove-Item $tempCsproj -ErrorAction SilentlyContinue
|
|
||||||
Write-Error "Build failed!"
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
Remove-Item $tempCsproj -ErrorAction SilentlyContinue
|
|
||||||
|
|
||||||
$pluginDll = Join-Path $buildOutput "$PluginName.dll"
|
|
||||||
if (-not (Test-Path $pluginDll)) {
|
|
||||||
Write-Error "Build output not found: $pluginDll"
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
Write-Host "Build successful: $pluginDll" -ForegroundColor Green
|
|
||||||
|
|
||||||
# --- Copy Dependencies for Reflection and ManifestGenerator ---
|
|
||||||
foreach ($dll in $requiredDlls) {
|
|
||||||
Copy-Item (Join-Path $DiscoBinPath $dll) $buildOutput -Force -ErrorAction SilentlyContinue
|
|
||||||
}
|
|
||||||
$extraDlls = @("EntityFramework.dll", "System.Web.Mvc.dll", "System.Web.WebPages.dll", "System.Web.Razor.dll", "RazorGenerator.Mvc.dll", "Newtonsoft.Json.dll")
|
|
||||||
foreach ($dep in $extraDlls) {
|
|
||||||
$depPath = Join-Path $DiscoBinPath $dep
|
|
||||||
if (Test-Path $depPath) { Copy-Item $depPath $buildOutput -Force -ErrorAction SilentlyContinue }
|
|
||||||
}
|
|
||||||
|
|
||||||
# --- Reflection-Based Feature Diagnostics ---
|
|
||||||
Write-Host "`n--- Plugin Assembly Diagnostics ---" -ForegroundColor Yellow
|
|
||||||
|
|
||||||
$reflectionOk = $true
|
|
||||||
try {
|
|
||||||
$pluginAsmBytes = [System.IO.File]::ReadAllBytes($pluginDll)
|
|
||||||
$pluginAsm = [System.Reflection.Assembly]::Load($pluginAsmBytes)
|
|
||||||
|
|
||||||
Write-Host " Plugin assembly loaded: $($pluginAsm.FullName)" -ForegroundColor Gray
|
|
||||||
|
|
||||||
# Find all types that extend UIExtensionFeature<T>
|
|
||||||
$uiExtTypes = @()
|
|
||||||
foreach ($t in $pluginAsm.GetExportedTypes()) {
|
|
||||||
$baseType = $t.BaseType
|
|
||||||
if ($baseType -ne $null -and $baseType.IsGenericType) {
|
|
||||||
$genDef = $baseType.GetGenericTypeDefinition()
|
|
||||||
if ($genDef.FullName -eq 'Disco.Services.Plugins.Features.UIExtension.UIExtensionFeature`1') {
|
|
||||||
$uiExtTypes += $t
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($uiExtTypes.Count -eq 0) {
|
|
||||||
Write-Warning " NO UIExtensionFeature<T> implementations found in assembly!"
|
|
||||||
Write-Warning " ExecuteAction will never be called without UIExtension features."
|
|
||||||
$reflectionOk = $false
|
|
||||||
} else {
|
|
||||||
Write-Host " Found $($uiExtTypes.Count) UIExtension feature(s):" -ForegroundColor Green
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach ($t in $uiExtTypes) {
|
|
||||||
$baseType = $t.BaseType
|
|
||||||
$genArgs = $baseType.GetGenericArguments()
|
|
||||||
$modelType = $genArgs[0]
|
|
||||||
$genDefFullName = $baseType.GetGenericTypeDefinition().FullName
|
|
||||||
|
|
||||||
Write-Host "`n Feature: $($t.FullName)" -ForegroundColor Cyan
|
|
||||||
Write-Host " Base type: $($baseType.FullName)" -ForegroundColor Gray
|
|
||||||
Write-Host " Generic definition: $genDefFullName" -ForegroundColor Gray
|
|
||||||
Write-Host " Model type: $($modelType.FullName)" -ForegroundColor Gray
|
|
||||||
Write-Host " Model assembly: $($modelType.Assembly.GetName().Name) v$($modelType.Assembly.GetName().Version)" -ForegroundColor Gray
|
|
||||||
|
|
||||||
# Verify [PluginFeature] attribute
|
|
||||||
$attrs = $t.GetCustomAttributes($true) | Where-Object { $_.GetType().Name -eq 'PluginFeatureAttribute' }
|
|
||||||
if ($attrs) {
|
|
||||||
foreach ($attr in $attrs) {
|
|
||||||
Write-Host " [PluginFeature] Id: $($attr.Id)" -ForegroundColor Gray
|
|
||||||
Write-Host " [PluginFeature] Name: $($attr.Name)" -ForegroundColor Gray
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Write-Warning " MISSING [PluginFeature] attribute! Disco cannot discover this feature."
|
|
||||||
$reflectionOk = $false
|
|
||||||
}
|
|
||||||
|
|
||||||
# Verify ExecuteAction is overridden (not just the abstract base)
|
|
||||||
$execMethod = $t.GetMethod('ExecuteAction', [System.Reflection.BindingFlags]'Public,Instance')
|
|
||||||
if ($execMethod -and $execMethod.DeclaringType -eq $t) {
|
|
||||||
Write-Host " ExecuteAction: overridden (OK)" -ForegroundColor Green
|
|
||||||
} elseif ($execMethod) {
|
|
||||||
Write-Warning " ExecuteAction: NOT overridden (declared in $($execMethod.DeclaringType.Name))"
|
|
||||||
$reflectionOk = $false
|
|
||||||
} else {
|
|
||||||
Write-Warning " ExecuteAction: NOT FOUND"
|
|
||||||
$reflectionOk = $false
|
|
||||||
}
|
|
||||||
|
|
||||||
# Verify Initialize is overridden and calls Register()
|
|
||||||
$initMethod = $t.GetMethod('Initialize', [System.Reflection.BindingFlags]'Public,Instance', $null, @([Type]'Disco.Data.Repository.DiscoDataContext'), $null)
|
|
||||||
if ($initMethod -and $initMethod.DeclaringType -eq $t) {
|
|
||||||
Write-Host " Initialize: overridden (OK)" -ForegroundColor Green
|
|
||||||
} else {
|
|
||||||
Write-Warning " Initialize: NOT overridden - Register() will never be called!"
|
|
||||||
$reflectionOk = $false
|
|
||||||
}
|
|
||||||
|
|
||||||
# Check that the model type implements BaseUIModel
|
|
||||||
$baseUIModel = $modelType.GetInterface('BaseUIModel')
|
|
||||||
if ($baseUIModel) {
|
|
||||||
Write-Host " BaseUIModel: $($modelType.Name) implements BaseUIModel (OK)" -ForegroundColor Green
|
|
||||||
} else {
|
|
||||||
Write-Warning " BaseUIModel: $($modelType.Name) does NOT implement BaseUIModel!"
|
|
||||||
$reflectionOk = $false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# Check the main plugin class
|
|
||||||
$pluginClass = $pluginAsm.GetExportedTypes() | Where-Object {
|
|
||||||
$_.BaseType -ne $null -and $_.BaseType.FullName -eq 'Disco.Services.Plugins.Plugin'
|
|
||||||
}
|
|
||||||
if ($pluginClass) {
|
|
||||||
Write-Host "`n Plugin class: $($pluginClass.FullName)" -ForegroundColor Cyan
|
|
||||||
$pluginAttrs = $pluginClass.GetCustomAttributes($true) | Where-Object { $_.GetType().Name -eq 'PluginAttribute' }
|
|
||||||
if ($pluginAttrs) {
|
|
||||||
foreach ($attr in $pluginAttrs) {
|
|
||||||
Write-Host " [Plugin] Id: $($attr.Id)" -ForegroundColor Gray
|
|
||||||
Write-Host " [Plugin] Name: $($attr.Name)" -ForegroundColor Gray
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Write-Warning " MISSING [Plugin] attribute!"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch {
|
|
||||||
Write-Warning " Reflection diagnostics failed: $($_.Exception.Message)"
|
|
||||||
Write-Warning " This may indicate assembly load issues (missing dependencies, version mismatches)."
|
|
||||||
$reflectionOk = $false
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($reflectionOk) {
|
|
||||||
Write-Host "`n All reflection checks PASSED" -ForegroundColor Green
|
|
||||||
} else {
|
|
||||||
Write-Host "`n Some reflection checks FAILED - see warnings above" -ForegroundColor Red
|
|
||||||
}
|
|
||||||
|
|
||||||
# --- Generate Manifest ---
|
|
||||||
Write-Host "`n--- Manifest Generation ---" -ForegroundColor Yellow
|
|
||||||
|
|
||||||
$useManifestGen = $false
|
|
||||||
$manifestGenExe = Join-Path $DiscoBinPath "Disco.Services.Plugins.ManifestGenerator.exe"
|
|
||||||
if (-not (Test-Path $manifestGenExe)) {
|
|
||||||
$manifestGenExe = Join-Path (Split-Path $DiscoBinPath -Parent) "Disco.Services.Plugins.ManifestGenerator.exe"
|
|
||||||
}
|
|
||||||
if (Test-Path $manifestGenExe) {
|
|
||||||
Write-Host "Found ManifestGenerator: $manifestGenExe" -ForegroundColor Gray
|
|
||||||
$useManifestGen = $true
|
|
||||||
|
|
||||||
# Capture stdout and stderr separately for diagnostics
|
|
||||||
$manifestGenOutput = & $manifestGenExe $pluginDll 2>&1
|
|
||||||
$manifestGenExit = $LASTEXITCODE
|
|
||||||
|
|
||||||
if ($manifestGenOutput) {
|
|
||||||
Write-Host " ManifestGenerator output:" -ForegroundColor DarkGray
|
|
||||||
$manifestGenOutput | ForEach-Object {
|
|
||||||
$line = $_.ToString()
|
|
||||||
if ($_ -is [System.Management.Automation.ErrorRecord]) {
|
|
||||||
Write-Host " [STDERR] $line" -ForegroundColor Red
|
|
||||||
} else {
|
|
||||||
Write-Host " $line" -ForegroundColor DarkGray
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($manifestGenExit -ne 0) {
|
|
||||||
Write-Warning "ManifestGenerator exited with code $manifestGenExit, falling back to manual manifest..."
|
|
||||||
$useManifestGen = $false
|
|
||||||
} else {
|
|
||||||
Write-Host "ManifestGenerator completed successfully" -ForegroundColor Green
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Write-Host "ManifestGenerator not found at:" -ForegroundColor DarkGray
|
|
||||||
Write-Host " $DiscoBinPath" -ForegroundColor DarkGray
|
|
||||||
Write-Host " $(Split-Path $DiscoBinPath -Parent)" -ForegroundColor DarkGray
|
|
||||||
Write-Host "Falling back to manual manifest generation..." -ForegroundColor Yellow
|
|
||||||
}
|
|
||||||
|
|
||||||
if (-not $useManifestGen) {
|
|
||||||
$version = [System.Reflection.AssemblyName]::GetAssemblyName($pluginDll).Version
|
|
||||||
|
|
||||||
# CategoryTypeName must match the .NET generic type definition FullName.
|
|
||||||
# For UIExtensionFeature<T>, this is UIExtensionFeature`1 (one backtick, then 1).
|
|
||||||
# In PowerShell double-quoted strings, `` (two backticks) produces one literal backtick.
|
|
||||||
$uiExtCategoryTypeName = "Disco.Services.Plugins.Features.UIExtension.UIExtensionFeature``1"
|
|
||||||
|
|
||||||
$manifest = [ordered]@{
|
|
||||||
Id = $PluginName
|
|
||||||
Name = "AD Compare"
|
|
||||||
Author = "Jess Rogerson"
|
|
||||||
Url = "https://gitea.hideawaygaming.com.au/jessikitty/disco-ad-compare-plugin"
|
|
||||||
Version = $version.ToString()
|
|
||||||
AssemblyPath = "$PluginName.dll"
|
|
||||||
TypeName = "Disco.Plugins.ADCompare.ADComparePlugin"
|
|
||||||
ConfigurationHandlerTypeName = "Disco.Plugins.ADCompare.ConfigurationHandler.ADCompareConfigurationHandler"
|
|
||||||
WebHandlerTypeName = "Disco.Plugins.ADCompare.WebHandler.ADCompareWebHandler"
|
|
||||||
Features = @(
|
|
||||||
[ordered]@{
|
|
||||||
Id = "ADCompareDeviceUI"
|
|
||||||
Name = "Device Page - AD Compare"
|
|
||||||
TypeName = "Disco.Plugins.ADCompare.Features.DeviceUIExtension"
|
|
||||||
PrimaryFeature = $false
|
|
||||||
CategoryTypeName = $uiExtCategoryTypeName
|
|
||||||
},
|
|
||||||
[ordered]@{
|
|
||||||
Id = "ADCompareUserUI"
|
|
||||||
Name = "User Page - AD Compare"
|
|
||||||
TypeName = "Disco.Plugins.ADCompare.Features.UserUIExtension"
|
|
||||||
PrimaryFeature = $false
|
|
||||||
CategoryTypeName = $uiExtCategoryTypeName
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
$manifestJson = $manifest | ConvertTo-Json -Depth 5
|
|
||||||
$manifestJson | Set-Content (Join-Path $buildOutput "manifest.json") -Encoding UTF8
|
|
||||||
Write-Host "Manual manifest.json created" -ForegroundColor Green
|
|
||||||
}
|
|
||||||
|
|
||||||
# --- Validate Manifest ---
|
|
||||||
Write-Host "`n--- Manifest Validation ---" -ForegroundColor Yellow
|
|
||||||
|
|
||||||
$manifestPath = Join-Path $buildOutput "manifest.json"
|
|
||||||
if (-not (Test-Path $manifestPath)) {
|
|
||||||
Write-Error "manifest.json NOT FOUND in build output! Plugin cannot be loaded by Disco."
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
$manifestContent = Get-Content $manifestPath -Raw
|
|
||||||
$manifestObj = $manifestContent | ConvertFrom-Json
|
|
||||||
|
|
||||||
# Basic structure checks
|
|
||||||
$manifestErrors = @()
|
|
||||||
$manifestWarnings = @()
|
|
||||||
|
|
||||||
if (-not $manifestObj.Id) { $manifestErrors += "Missing 'Id'" }
|
|
||||||
if (-not $manifestObj.TypeName) { $manifestErrors += "Missing 'TypeName'" }
|
|
||||||
if (-not $manifestObj.AssemblyPath) { $manifestErrors += "Missing 'AssemblyPath'" }
|
|
||||||
if (-not $manifestObj.Version) { $manifestWarnings += "Missing 'Version'" }
|
|
||||||
|
|
||||||
Write-Host " Id: $($manifestObj.Id)" -ForegroundColor Gray
|
|
||||||
Write-Host " Version: $($manifestObj.Version)" -ForegroundColor Gray
|
|
||||||
Write-Host " TypeName: $($manifestObj.TypeName)" -ForegroundColor Gray
|
|
||||||
|
|
||||||
if (-not $manifestObj.Features -or $manifestObj.Features.Count -eq 0) {
|
|
||||||
$manifestErrors += "Features array is EMPTY or MISSING - no UIExtensions will be loaded!"
|
|
||||||
} else {
|
|
||||||
Write-Host " Features: $($manifestObj.Features.Count) found" -ForegroundColor Green
|
|
||||||
|
|
||||||
# Expected CategoryTypeName (with literal backtick)
|
|
||||||
$expectedCategory = 'Disco.Services.Plugins.Features.UIExtension.UIExtensionFeature`1'
|
|
||||||
|
|
||||||
foreach ($feature in $manifestObj.Features) {
|
|
||||||
Write-Host "`n Feature: $($feature.Id)" -ForegroundColor Cyan
|
|
||||||
Write-Host " Name: $($feature.Name)" -ForegroundColor Gray
|
|
||||||
Write-Host " TypeName: $($feature.TypeName)" -ForegroundColor Gray
|
|
||||||
Write-Host " CategoryTypeName: $($feature.CategoryTypeName)" -ForegroundColor Gray
|
|
||||||
Write-Host " PrimaryFeature: $($feature.PrimaryFeature)" -ForegroundColor Gray
|
|
||||||
|
|
||||||
if (-not $feature.TypeName) {
|
|
||||||
$manifestErrors += "Feature '$($feature.Id)' is missing TypeName"
|
|
||||||
}
|
|
||||||
|
|
||||||
if (-not $feature.CategoryTypeName) {
|
|
||||||
$manifestErrors += "Feature '$($feature.Id)' is missing CategoryTypeName"
|
|
||||||
} elseif ($feature.CategoryTypeName -ne $expectedCategory) {
|
|
||||||
$manifestErrors += "Feature '$($feature.Id)' CategoryTypeName MISMATCH!"
|
|
||||||
Write-Host " Expected: $expectedCategory" -ForegroundColor Red
|
|
||||||
Write-Host " Got: $($feature.CategoryTypeName)" -ForegroundColor Red
|
|
||||||
|
|
||||||
# Char-by-char comparison to identify the exact difference
|
|
||||||
$expChars = $expectedCategory.ToCharArray()
|
|
||||||
$actChars = $feature.CategoryTypeName.ToCharArray()
|
|
||||||
for ($i = 0; $i -lt [Math]::Max($expChars.Length, $actChars.Length); $i++) {
|
|
||||||
$ec = if ($i -lt $expChars.Length) { $expChars[$i] } else { $null }
|
|
||||||
$ac = if ($i -lt $actChars.Length) { $actChars[$i] } else { $null }
|
|
||||||
if ($ec -ne $ac) {
|
|
||||||
$ecDisplay = if ($ec) { "'$ec' (0x{0:X2})" -f [int][char]$ec } else { '[END]' }
|
|
||||||
$acDisplay = if ($ac) { "'$ac' (0x{0:X2})" -f [int][char]$ac } else { '[END]' }
|
|
||||||
Write-Host " First difference at position ${i}: expected $ecDisplay vs got $acDisplay" -ForegroundColor Red
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Write-Host " CategoryTypeName: VALID" -ForegroundColor Green
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# Show raw manifest for manual inspection
|
|
||||||
Write-Host "`n --- Raw manifest.json ---" -ForegroundColor DarkGray
|
|
||||||
$manifestContent -split "`n" | ForEach-Object {
|
|
||||||
Write-Host " $_" -ForegroundColor DarkGray
|
|
||||||
}
|
|
||||||
Write-Host " --- End manifest.json ---" -ForegroundColor DarkGray
|
|
||||||
|
|
||||||
if ($manifestErrors.Count -gt 0) {
|
|
||||||
Write-Host "`n MANIFEST ERRORS:" -ForegroundColor Red
|
|
||||||
foreach ($err in $manifestErrors) {
|
|
||||||
Write-Host " [ERROR] $err" -ForegroundColor Red
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ($manifestWarnings.Count -gt 0) {
|
|
||||||
foreach ($warn in $manifestWarnings) {
|
|
||||||
Write-Host " [WARN] $warn" -ForegroundColor Yellow
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ($manifestErrors.Count -eq 0) {
|
|
||||||
Write-Host "`n All manifest checks PASSED" -ForegroundColor Green
|
|
||||||
} else {
|
|
||||||
Write-Host "`n Manifest has errors - UIExtensions will NOT render!" -ForegroundColor Red
|
|
||||||
Write-Host " Fix the errors above before installing the plugin." -ForegroundColor Red
|
|
||||||
}
|
|
||||||
|
|
||||||
# --- Package ---
|
|
||||||
Write-Host "`nPackaging .discoPlugin file..." -ForegroundColor Yellow
|
|
||||||
|
|
||||||
$includeFiles = @("$PluginName.dll", "$PluginName.pdb", "manifest.json")
|
|
||||||
|
|
||||||
if (-not $version) {
|
|
||||||
$version = [System.Reflection.AssemblyName]::GetAssemblyName($pluginDll).Version
|
|
||||||
}
|
|
||||||
|
|
||||||
$packageName = "$PluginName-$($version.ToString()).discoPlugin"
|
|
||||||
$packagePath = Join-Path $PluginDir $packageName
|
|
||||||
|
|
||||||
Get-ChildItem $PluginDir -Filter "*.discoPlugin" | Remove-Item -Force
|
|
||||||
Get-ChildItem $PluginDir -Filter "*.zip" | Where-Object { $_.Name -like "$PluginName*" } | Remove-Item -Force
|
|
||||||
|
|
||||||
$tempPkg = Join-Path $env:TEMP "discoplugin_$([guid]::NewGuid().ToString('N'))"
|
|
||||||
New-Item -ItemType Directory -Path $tempPkg -Force | Out-Null
|
|
||||||
$missingFiles = @()
|
|
||||||
foreach ($f in $includeFiles) {
|
|
||||||
$src = Join-Path $buildOutput $f
|
|
||||||
if (Test-Path $src) {
|
|
||||||
Copy-Item $src $tempPkg -Force
|
|
||||||
} else {
|
|
||||||
$missingFiles += $f
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($missingFiles.Count -gt 0) {
|
|
||||||
Write-Warning "Missing files not included in package: $($missingFiles -join ', ')"
|
|
||||||
}
|
|
||||||
|
|
||||||
Write-Host "Files in package:" -ForegroundColor Gray
|
|
||||||
Get-ChildItem $tempPkg -File | ForEach-Object {
|
|
||||||
$size = [math]::Round($_.Length / 1KB, 1)
|
|
||||||
Write-Host " $($_.Name) ($size KB)" -ForegroundColor Gray
|
|
||||||
}
|
|
||||||
|
|
||||||
$zipPath = Join-Path $PluginDir "$PluginName-$($version.ToString()).zip"
|
|
||||||
Compress-Archive -Path "$tempPkg\*" -DestinationPath $zipPath -Force
|
|
||||||
Rename-Item $zipPath $packageName -Force
|
|
||||||
Remove-Item $tempPkg -Recurse -Force
|
|
||||||
|
|
||||||
# --- Final Package Verification ---
|
|
||||||
Write-Host "`n--- Package Verification ---" -ForegroundColor Yellow
|
|
||||||
try {
|
|
||||||
Add-Type -AssemblyName System.IO.Compression.FileSystem
|
|
||||||
$zip = [System.IO.Compression.ZipFile]::OpenRead($packagePath)
|
|
||||||
$entries = $zip.Entries | Select-Object -ExpandProperty Name
|
|
||||||
$zip.Dispose()
|
|
||||||
|
|
||||||
$requiredEntries = @("$PluginName.dll", "manifest.json")
|
|
||||||
foreach ($req in $requiredEntries) {
|
|
||||||
if ($entries -contains $req) {
|
|
||||||
Write-Host " [OK] $req" -ForegroundColor Green
|
|
||||||
} else {
|
|
||||||
Write-Host " [MISSING] $req" -ForegroundColor Red
|
|
||||||
}
|
|
||||||
}
|
|
||||||
# Show any extras
|
|
||||||
foreach ($entry in $entries) {
|
|
||||||
if ($entry -notin $requiredEntries) {
|
|
||||||
Write-Host " [+] $entry" -ForegroundColor Gray
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
Write-Warning " Could not verify package contents: $($_.Exception.Message)"
|
|
||||||
}
|
|
||||||
|
|
||||||
$packageSize = [math]::Round((Get-Item $packagePath).Length / 1KB, 1)
|
|
||||||
Write-Host "`n=== Package created: $packagePath ($($packageSize) KB) ===" -ForegroundColor Green
|
|
||||||
Write-Host "Import into Disco ICT via: Configuration > Plugins > Install Plugin" -ForegroundColor Cyan
|
|
||||||
|
|
||||||
# --- Summary ---
|
|
||||||
$hasIssues = (-not $reflectionOk) -or ($manifestErrors.Count -gt 0)
|
|
||||||
if ($hasIssues) {
|
|
||||||
Write-Host "`n=== BUILD COMPLETED WITH WARNINGS ===" -ForegroundColor Yellow
|
|
||||||
Write-Host "Review the diagnostics above before installing." -ForegroundColor Yellow
|
|
||||||
Write-Host "If UIExtensions still don't render after install:" -ForegroundColor Yellow
|
|
||||||
Write-Host " 1. Check Disco logs for feature initialization messages" -ForegroundColor Yellow
|
|
||||||
Write-Host " 2. View page source and search for 'layout_uiExtensions'" -ForegroundColor Yellow
|
|
||||||
Write-Host " - Present but empty = ExecuteAction returned Nothing()/null" -ForegroundColor Yellow
|
|
||||||
Write-Host " - Missing entirely = Feature not registered (Register() not called)" -ForegroundColor Yellow
|
|
||||||
Write-Host " 3. Check the installed manifest at:" -ForegroundColor Yellow
|
|
||||||
Write-Host " App_Data\Plugins\$PluginName\manifest.json" -ForegroundColor Yellow
|
|
||||||
} else {
|
|
||||||
Write-Host "`n=== BUILD COMPLETED SUCCESSFULLY ===" -ForegroundColor Green
|
|
||||||
}
|
|
||||||
Reference in new issue
Block a user