Я хотел бы использовать API NDepend из сценария Windows PowerShell 5.1, но это может быть невозможно из-за того, что Windows PowerShell не может обрабатывать свойства с тем же именем, но с другим регистром.
Вот код PowerShell, который работает на данный момент:
using namespace NDepend
Set-StrictMode -Version Latest
$AssList = "NDepend.API", "NDepend.Core", "NDepend.Platform.DotNet", "NDepend.Analysis", "NDepend.Platform.DotNet.NetFx", "NDepend.UI.NetFx"
$AssList.ForEach{
$AssPfad = Join-Path -Path "D:\Program Files\NDepend\Lib" -ChildPath "$_.dll"
Add-Type -Path $AssPfad
}
$ndprojPath = "D:\Test1234.ndproj"
# Still using NDepend in the namespace for clarity (although it is not necessary)
$ndprojPathAbsolute = [NDepend.Path.PathHelpers]::ToAbsoluteFilePath($ndprojPath)
$ndProvider = [NDepend.NDependServicesProvider]::new()
$ndManager = $ndProvider.ProjectManager
$project = $ndManager.LoadProject($ndprojPathAbsolute)
$outputDirPath = Join-Path -Path $PSScriptRoot -ChildPath "OutputDir"
$project.Properties.OutputDir = [NDepend.Path.PathHelpers]::ToDirectoryPath($outputDirPath)
$analysisResult = [NDepend.Analysis.ExtensionMethodsProjectAnalysis]::RunAnalysisAndBuildReport($project)
$ndCodebase = $analysisResult.CodeBase
Вывод $ndCodebase приводит к ошибке
format-default : # The field or property "F18r" for type "a.QHkY" differs only in letter casing from the field or property "f18R". The type must be Common Language Specification (CLS) compliant
Остальная часть скрипта работает и выводит результат анализа:
$Issues = [Analysis.ExtensionMethodsProjectAnalysis]::ComputeIssues($analysisResult)
$ndIssuesSet = $Issues
$ndIssuesSet.AllRules
На данный момент я не знаю, как избежать этой ошибки.
PS: Код можно воспроизвести только при наличии лицензии на сборку.
Обновление: благодаря комментарию и предложениям mklement0 я смог задуматься о свойстве, вызывающем ошибку.
$Ass = [AppDomain]::CurrentDomain.GetAssemblies().Where{$_.GetName().Name -eq "NDepend.Core"}[0]
$t = $ass.gettype("a.QHkY", $True)
# Just for information purpose
$t.GetProperties().Where{$_.Name -match "f18r"}
# Not getting this property with reflection due to casing?
$PropInfo1 = $ass.gettype("a.QHkY", $True).GetProperty("f18r", [System.Reflection.BindingFlags]::IgnoreCase)
# Getting this property
$PropInfo2 = $ass.gettype("a.QHkY", $True).GetProperty("F18r")
# No value since $PropInfo1 is $null
$PropInfo1.GetValue($analysisResult.CodeBase)
# Getting a value
$PropInfo2.GetValue($analysisResult.CodeBase)
Но поскольку мне нужно значение свойства CodeBase:
# Get the value of the CodeBase property
$PropInfo = $ass.gettype("a.vHLF", $True).GetProperty("CodeBase")
$Value = $PropInfo.GetValue($ndCodebase)
$Value
Я получу ту же ошибку формата по умолчанию и вернусь к исходной точке. $Value содержит значение объекта CodeBase, но похоже, что ошибку вызывает форматтер PowerShell.
Обновление: как предположил mklement0, все сводится к тому, чтобы «воссоздать» объект как пользовательский тип и необходимые свойства.
Я сделал это для всех, кто заинтересован в решении с использованием кода C# внутри сценария Powershell. Но, в конце концов, это, вероятно, слишком много усилий.
using namespace NDepend
Set-StrictMode -Version Latest
$AssList = "NDepend.API", "NDepend.Core", "NDepend.Platform.DotNet", "NDepend.Analysis", "NDepend.Platform.DotNet.NetFx", "NDepend.UI.NetFx"
$AssList.ForEach{
$AssPfad = Join-Path -Path "D:\Program Files\NDepend\Lib" -ChildPath "$_.dll"
Add-Type -Path $AssPfad
}
$ndprojPath = "D:\Test.ndproj"
# Still using NDepend in the namespace for clarity (although it is not necessary)
$ndprojPathAbsolute = [Path.PathHelpers]::ToAbsoluteFilePath($ndprojPath)
$ndProvider = [NDepend.NDependServicesProvider]::new()
$ndManager = $ndProvider.ProjectManager
$project = $ndManager.LoadProject($ndprojPathAbsolute)
$analysisResult = [NDepend.Analysis.ExtensionMethodsProjectAnalysis]::RunAnalysis($project)
# Reflection done in C# to avoid dealing with a CodeBase object directly in PowerShell
$CSCode = @"
using NDepend.Analysis;
using NDepend.Issue;
using NDepend.CodeModel;
using System;
using System.Collections.Generic;
using System.Linq;
public class CodeBaseInfo
{
public ICodeBase CodeBase { get; set; }
public int NbLinesOfCode { get; set; }
public CodeBaseInfo(IAnalysisResult analysisResult)
{
this.CodeBase = analysisResult.CodeBase;
var ass = AppDomain.CurrentDomain.GetAssemblies().Where(a=>a.GetName().Name == "NDepend.Core").First();
var propInfo = ass.GetType("a.vHLF").GetProperty("CodeBase");
var propValue = propInfo.GetValue(analysisResult);
propInfo = ass.GetType("a.QHkY").GetProperty("NbLinesOfCode");
this.NbLinesOfCode = Convert.ToInt32(propInfo.GetValue(propValue));
}
// Will result to the format error - only for demonstration purposes
public ICodeBase GetCodeBase()
{
return this.CodeBase;
}
public ICodeBaseView GetApplication() {
return this.CodeBase.Application;
}
public int GetNbLinesOfCode() {
return this.NbLinesOfCode;
}
}
"@
# Please check the path of the NDepend.API.dll
Add-Type -TypeDefinition $CSCode -ReferencedAssemblies "D:\Program Files\NDepend\Lib\NDepend.API.dll", "netstandard"
$CodeBaseInfo = [CodeBaseInfo]::new($analysisResult)
# Same format error
# $CodeBaseInfo.CodeBase
# This works...
$CodeBaseInfo.GetApplication()
$CodeBaseInfo.GetNbLinesOfCode()
Громоздким обходным решением, которое снова позволило бы вам работать с целыми объектами, было бы создание пользовательского объекта, который эмулирует исходный экземпляр, используя отражение для получения интересующих значений свойств/полей, как показано, и добавляя их в пользовательский объект.
Спасибо за этот момент, это из-за обфускации, мы проверяем, можно ли избежать такого конфликта имен.
Действительно, PowerShell явно проверяет типы .NET на наличие членов, имена которых являются вариациями регистра друг друга (например, F18r
против f18r
); любой такой тип по определению не CLS-совместим.
В Windows PowerShell и, по крайней мере, до версии 7.4.x, а также в PowerShell (Core) 7 обнаружение изменения регистра в именах свойств или полей приводит к ошибке (завершающей оператор) (исключение .NET) при доступе к любому из членов данного типа предпринимается попытка (в то время как в отношении методов такие вариации регистра спокойно допускаются - при условии, что также нет вариаций регистра свойств/полей - но пока один из них вызывается, вы не можете предсказуемо вызвать данный вариант) .
Проблема GitHub #10303 — это предложение, получившее зеленый свет по устранению этой проблемы, но никто еще не приступил к его реализации.
.F18r
и .f18r
будут успешными и получат доступ к свойству/полю, которое точно соответствует регистру, но вариант, который не соответствует точно, все равно будет сообщать об ошибке, например. .F18R
.Обходной путь:
На данный момент единственный способ избежать ошибок в этом контексте — использовать отражение .NET при доступе к членам свойств и полей, которые имеют вариации регистра.
Обратите внимание, что к такому свойству и полям можно получить неявный доступ (и, следовательно, вызвать ошибки) при выводе экземпляров их типов на хост (отображение) благодаря богатой системе форматирования PowerShell for-display , которая по умолчанию представляет собой непримитивный . NET по их общедоступным свойствам и полям — вот почему при выводе $ndCodebase
(опираясь на поведение PowerShell неявного вывода) у вас возникла ошибка.
Вам необходимо знать конкретный тип данного экземпляра, чтобы использовать обходной путь отражения, поскольку метод отражения должен быть вызван для типа, чтобы избежать ошибки - тип интерфейса может работать, а может и не работать (в то время как вызов любого члена в экземпляр, включая метод .GetType()
, неизменно вызывает возникшую ошибку).
.GetType()
в экземпляре вызовет возникшую ошибку, поскольку ошибку вызывает любой доступ к члену экземпляра (тогда как построение экземпляра в целом работает нормально).В вашем случае в документации типы значений свойств выражаются только как интерфейсы, а интерфейс, который реализует значение свойства .CodeBase, по-видимому, не охватывает элементы с вариантами регистра, которые вызывают ошибку.
Однако у вас должна быть возможность определить полный тип конкретного типа с помощью внутреннего свойства .pstypenames:
[Type] $concretePublicType =
$ndCodebase.pstypenames.Where({ ($_ -as [type]).IsPublic }, 'First')[0]
Используя полный конкретный тип, вы можете использовать отражение для доступа, например, к значению поля/свойства .F18r
(точно по регистру) следующим образом, предполагая, что это свойство (если это поле, замените .GetProperty()
на .GetField()
:
$concretePublicType.GetProperty('F18r').GetValue($ndCodebase)
Я обновил свой ответ, чтобы прояснить следующее: проблема возникает всякий раз, когда вы получаете доступ к любому члену экземпляра, тип которого имеет члены с именами, которые являются вариациями регистра друг друга. Поскольку форматирование PowerShell для отображения перечисляет элементы, когда вы (неявно) выводите экземпляр (например, отправляете
$Value
отдельно), тогда возникает ошибка. Таким образом, вы не сможете работать с такими экземплярами целиком: вы можете обращаться к их свойствам и полям (и вызывать их методы) только по одному, посредством отражения.