1

I'm trying to use Newtonsoft.Json library:

[Reflection.Assembly]::LoadFile( 'C:\Users\user\Documents\WindowsPowerShell\Modules\newtonsoft.json\1.0.2.201\libs\Newtonsoft.Json.dll' )

Add-Type -TypeDefinition @"
using Newtonsoft.Json;

class BigIntegerConverter: JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return (objectType == typeof(Org.BouncyCastle.Math.BigInteger));
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        System.Numerics.BigInteger big = (System.Numerics.BigInteger)reader.Value;
        return new Org.BouncyCastle.Math.BigInteger(big.ToString());
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        writer.WriteRawValue(value.ToString());
    }
}
"@

and getting the next error:

The type or namespace name 'Newtonsoft' could not be found (are you missing a using directive or an assembly reference?)

Why it can't find Newtonsoft tho it loaded? I can call it in PS code via [Newtonsoft.Json.JsonConverter], but not in type definitions!

Thanks!

MegaBomber
  • 345
  • 2
  • 3
  • 11
  • Please add your `$PsVersionTable` to your question – Santiago Squarzon Jun 21 '23 at 23:20
  • 4
    `Add-Type` offloads most of its work to an external compiler, so the state of the current application domain doesn't really affect the libraries available to the code you're trying to compile - you probably want `Add-Type -TypeDefinition @"..."@ -ReferencedAssemblies 'path\to\Newtonsoft.Json.dll'`... – Mathias R. Jessen Jun 21 '23 at 23:23
  • 5.1 ........... – MegaBomber Jun 21 '23 at 23:24
  • 1
    You're kinda my personal .NET Guardian angel :) This err is fixed, but now it can't found System.Object: The type 'System.Object' is defined in an assembly that is not referenced :( – MegaBomber Jun 21 '23 at 23:37
  • Honestly, I just need to inherit Newtonsoft.Json.JsonConverter. But as I understood, I can't inherit directly .NET class from PS. So that I'm trying to do all of this... – MegaBomber Jun 21 '23 at 23:40

1 Answers1

2

Assuming you load the Newtonsoft.Json.dll and BouncyCastle.Cryptography.dll assemblies before invoking your script - e.g., with Add-Type -LiteralPath 'C:\Users\user\Documents\WindowsPowerShell\Modules\newtonsoft.json\1.0.2.201\libs\Newtonsoft.Json.dll' - you can define your class using PowerShell code, i.e. a PowerShell class definition, as follows:

# Note: Assumes that Newtonsoft.Json.dll and BouncyCastle.Cryptography.dll
#       were loaded *before* running this script.
class BigIntegerConverter : Newtonsoft.Json.JsonConverter {
    [bool] CanConvert([Type] $objectType){
        return $objectType -is [Org.BouncyCastle.Math.BigInteger]
    }

    [object] ReadJson([Newtonsoft.Json.JsonReader] $reader, [Type] $objectType, [object] $existingValue, [Newtonsoft.Json.JsonSerializer] $serializer) {
        [bigint] $big = $reader.Value
        return [Org.BouncyCastle.Math.BigInteger]::new($big.ToString())
    }

    WriteJson([Newtonsoft.Json.JsonWriter] $writer, [object] $value, [Newtonsoft.Json.JsonSerializer] $serializer) {
        $writer.WriteRawValue($value.ToString())
    }
}

The - unfortunate - requirement that any types referenced in a PowerShell class have to have been loaded before the script at hand is parsed is discussed in this answer.


As for what you tried:

Mathias is correct in that you normally only need to make sure that the assemblies needed for compiling the C# code passed to Add-Type be passed to -ReferencedAssemblies.

For reasons unknown to me, with Newtonsoft.Json.dll specifically, the code below additionally requires loading the Newtonsoft.Json.dll assembly beforehand too.

  • Note: The code below works in Windows PowerShell only - unlike the PowerShell class implementation above, which works in both editions.
    • However, it can be made to work in PowerShell (Core) 7+, with extra effort:
      • Use the .NET Standard 2.0 implementation of NewtonSoft.Json.dll (all implementations are part of the same Newtonsoft.Json NuGet package)

        • In the -ReferencedAssemblies argument:
          • Add netstandard
          • Replace System.Numerics with System.Runtime.Numerics
        • You then do not load Newtonsoft.Json.dll beforehand with Add-Type -LiteralPath.
      • Alternatively, use the .NET (Core) implementation of the assembly:

        • Follow the same steps as above, except for adding netstandard
        • Add -IgnoreWarnings -WarningAction Ignore to the Add-Type call, to ignore and silence warnings regarding a version mismatch between PowerShell's .NET runtime version and the one targeted by Newtonsoft.Json.dll
      • If you accidentally use a .NET Framework implementation, you'll get errors about not finding core types such as Object, presumably because they're looked for in .NET Framework assemblies.

$jsonAssembly = 'C:\Users\user\Documents\WindowsPowerShell\Modules\newtonsoft.json\1.0.2.201\libs\Newtonsoft.Json.dll'
# Specify the path to BouncyCastle.Cryptography.dll here.
$bouncyCastleAssembly = 'C:\path\to\BouncyCastle.Cryptography.dll'

# !! Inexplicably, this is needed too, possibly also for BouncyCastle.Cryptography.dll
Add-Type -LiteralPath $jsonAssembly

Add-Type -ReferencedAssemblies $jsonAssembly, $bouncyCastleAssembly, System.Numerics -TypeDefinition @'
using System;
using System.Numerics;
using Newtonsoft.Json;

public class BigIntegerConverter: JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return (objectType == typeof(Org.BouncyCastle.Math.BigInteger));
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        BigInteger big = (BigInteger)reader.Value;
        return new Org.BouncyCastle.Math.BigInteger(big.ToString());
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        writer.WriteRawValue(value.ToString());
    }
'@

Also note:

  • The addition of using System; to enable namespace-free references to class Type

  • The addition of System.Numerics to -ReferencedAssemblies as well as using System.Numerics; to allow namespace-free references to BigInteger

  • Adding public to the BigInterConverter class definition to ensure that the resulting class is public.

mklement0
  • 382,024
  • 64
  • 607
  • 775
  • Thanks you! Unfortunately, I still have an error: Add-Type : c:\Users\user\AppData\Local\Temp\kpspmcfd.0.cs(5) : The type 'System.Object' is defined in an assembly that is not referenced. You must add a reference to assembly 'System.Runtime, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' – MegaBomber Jun 22 '23 at 10:02
  • I suppose System.Object should be in the System namespace, but it seems it doesn't :( – MegaBomber Jun 22 '23 at 10:03
  • 1
    Looks like I've fixed it :) Thank you guys! – MegaBomber Jun 22 '23 at 10:11
  • Glad to hear you fixed it, @MegaBomber. I see the "'Object' is defined in an assembly that is not referenced [...]" error only in PowerShell v7+, not in Windows PowerShell - I've updated the answer to make that clear. Seemingly, it looks for .NET Framework rather than .NET (Core) assemblies - not sure why. By contrast, the PowerShell `class` implementation works in both editions. – mklement0 Jun 22 '23 at 12:38
  • @MegaBomber, I've found a way to make it work in PowerShell v7+ too - please see my update. (The "Object" errors stemmed from accidental use of the .NET Framework Newtonsoft assembly) – mklement0 Jun 22 '23 at 14:11