powershellndependcls

Using the NDepend API from a Windows PowerShell 5.1 script results to a CLS letter casing error


I would like to use the NDepend API from a Windows PowerShell 5.1 script but may be its not possible due to the fact that Windows PowerShell cannot handle properties with the same name but different casing.

Here is the PowerShell code that works so far:

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

Outputting $ndCodebase leads to an error

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

The rest of the script works and will output the result on an analysis:

$Issues = [Analysis.ExtensionMethodsProjectAnalysis]::ComputeIssues($analysisResult)
$ndIssuesSet = $Issues 
$ndIssuesSet.AllRules

At the moment I am not sure how to avoid this error.

PS: The code is only reproducible with a build license.

Update: Thanks to the comment and suggestions by mklement0 I was able to reflect on the property that causes the error

$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)

But since I want the value of the CodeBase property:

# Get the value of the CodeBase property
$PropInfo = $ass.gettype("a.vHLF", $True).GetProperty("CodeBase")
$Value = $PropInfo.GetValue($ndCodebase)
$Value

I'll get the same format-default error and I am back to square one. $Value contains the value of the CodeBase object but it looks like the PowerShell formatter causes the error.

Update: As mklement0 suggested it boils down to have to "re-create" the object as a custom type and the needed properties.

I did this for anyone who is interested in a solution by using C# code inside the Powershell script. But in the end its probably too much effort.

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()

Solution

  • Indeed, PowerShell explicitly checks .NET types for having members whose names are case variations of one another (e.g. F18r vs. f18r); any such type is by definition not CLS-compliant.


    Workaround:

    For now, the only way to avoid errors in this context is to use .NET reflection when accessing property and field members that have case variations.

    Using the full, concrete type, you can use reflection to access, for instance, the .F18r field / property value - case-exactly - as follows, assuming it is a property (if it is a field, replace .GetProperty() with .GetField():

    $concretePublicType.GetProperty('F18r').GetValue($ndCodebase)