Extending accelerated .Net class using the original constructors - powershell

Inspired by theses questions:
Get a specific octet from the string representation of an IPv4
address
Powershell - Checking IP Address range based on CSV file
I am trying to extend the IPAddress class with an ToBigInt() method in PowerShell (if even possible) to be able to easily compare (IPv4 and IPv6) addresses using comparison operations along with -lt and -gt. E.g.:
([IPAddress]'2001:db8::8a2e:370:7334').ToBigInt() -lt ([IPAddress]'2001:db8::8a2e:370:7335').ToBigInt()
My first attempt:
class IPAddress : System.Net.IPAddress {
[Void]ToBigInt() {
$BigInt = [BigInt]0
foreach ($Byte in $This.GetAddressBytes()) {
$BigInt = ($BigInt -shl 8) + $Byte
}
$BigInt
}
}
Causes an error:
Line |
1 | class IPAddress : System.Net.IPAddress {
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
| Base class 'IPAddress' does not contain a parameterless constructor.
So apparently, I need to add the ([IPAddress]::new) constructors to the sub class:
class IPAddress : System.Net.IPAddress {
IPAddress ([long]$NewAddress) : base($NewAddress) { }
IPAddress ([byte[]]$Address, [long]$ScopeId) : base($Address, $ScopeID) { }
IPAddress ([System.ReadOnlySpan[byte]]$Address, [long]$ScopeId) : base($Address, $ScopeID) { }
IPAddress ([byte[]]$Address) : base($Address) { }
IPAddress ([System.ReadOnlySpan[byte]]$Address) : base($Address) { }
[Void]ToBigInt() {
$BigInt = [BigInt]0
foreach ($Byte in $This.GetAddressBytes()) {
$BigInt = ($BigInt -shl 8) + $Byte
}
$BigInt
}
}
Now, the class defintion is accepted but when I try to create an IPAddress, like:
[IPAddress]'172.13.23.34'
I get a new error:
OperationStopped: Unable to cast object of type 'System.Net.IPAddress' to type 'IPAddress'.
How can I resolve this casting error?

Related

PowerShell Unable to find type

I'm having trouble with an error when I try to run my PowerShell script from the command line:
Unable to find type [System.Windows.Forms.ListViewItem]
I have tried different ways to import the required classes:
using assembly System.Windows.Forms
using namespace System.Windows.Forms
using namespace System.Drawing
Add-Type System.Windows.Forms
In PowerShell ISE, I can run my script without any errors. But when I try to execute my script from CMD it doesn't work. It's exactly the same code and the same script files. This is the command I use to execute the script in CMD followed by the error messages:
C:\Users\Admin>powershell -File "C:\Folder\Main.ps1"
At C:\Folder\Main.ps1:35 char:30
+ ... return (([int](([System.Windows.Forms.ListViewItem]$a).Sub ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Unable to find type [System.Windows.Forms.ListViewItem].
At C:\Folder\Main.ps1:35 char:114
+ ... umnIndex].Text)) - ([int](([System.Windows.Forms.ListViewItem]$b).Sub ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Unable to find type [System.Windows.Forms.ListViewItem].
At C:\Folder\Main.ps1:37 char:41
+ ... return ([String]::Compare(([System.Windows.Forms.ListViewItem]$a).Sub ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Unable to find type [System.Windows.Forms.ListViewItem].
At C:\Folder\Main.ps1:37 char:115
+ ... [$this.columnIndex].Text, ([System.Windows.Forms.ListViewItem]$b).Sub ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Unable to find type [System.Windows.Forms.ListViewItem].
+ CategoryInfo : ParserError: (:) [], ParentContainsErrorRecordException
+ FullyQualifiedErrorId : TypeNotFound
The error messages refer to the code below. This is my only class in that script:
class ListViewItemComparer : System.Collections.IComparer {
[int]$sortOrder
[String]$columnType
[int]$columnIndex
ListViewItemComparer() {
$this.sortOrder = 1
$this.columnType = "String"
$this.columnIndex = 0
}
ListViewItemComparer([int]$sortOrder, [String]$columnType, [int]$columnIndex) {
$this.sortOrder = $sortOrder
$this.columnType = $columnType
$this.columnIndex = $columnIndex
}
[void] ChangeSorting([int]$columnIndex, [String]$columnType) {
if($this.columnIndex -eq $columnIndex) {
$this.sortOrder *= -1
} else {
$this.columnIndex = $columnIndex
$this.sortOrder = 1
}
$this.columnType = $columnType
}
[int] Compare([Object]$a, [Object]$b) {
if($this.columnType -eq "Number") {
return (([int](([System.Windows.Forms.ListViewItem]$a).SubItems[$this.columnIndex].Text)) - ([int](([System.Windows.Forms.ListViewItem]$b).SubItems[$this.columnIndex].Text))) * $this.sortOrder
} else {
return ([String]::Compare(([System.Windows.Forms.ListViewItem]$a).SubItems[$this.columnIndex].Text, ([System.Windows.Forms.ListViewItem]$b).SubItems[$this.columnIndex].Text)) * $this.sortOrder
}
}
}
I'm using the type System.Windows.Forms.ListViewItem at other places too in my code, but there is no problem outside of a class. I don't even have to write the whole class path, just ``ListViewItem` works as well.
Can somebody tell me why the type System.Windows.Forms.ListViewItem is not recognized?
Why is it recognized outside the ListViewItemComparer class in the same script file?
Why does this error occur when I run the script from CMD and not in PowerShell ISE?

powershell access sibling method

I'm having trouble accessing sibling methods in powershell
$group.search.has_member(search) is the same once it gets the members, but
$group.retrieve.Members() gets the members differently for 365 groups vs local groups
So I'm trying to have .search.has_member(search) call .retrieve.Members() to minimize code duplication (IRL there's around 15 different versions some with unique retrieval methods, some with shared approaches). But I'm having trouble accessing the sibling's methods
My main goal is I'm trying to build an interface facade that will unify how you interact with Exchange mail objects. 365 mailboxes, local mailboxes, 365 groups, and local groups (among others) all can have Members, MemberOf, Email Addresses and Distinguished Name, but while DN is a property on all objects, Email addresses property is formatted differently on 365 vs local groups (which affects .MatchesSMTP($search)), and retrieving Members() is different for 365 groups vs local groups and should return null for mailboxes and mail contacts regardless of whether it's 365 or local, and retrieval of MemberOf is unique for each object type.
This leads to a significant level of complexity. Originally I was trying to break them out using inheritance based first on Type(mailbox vs group) then on Server(mailbox_365, mailbox_local, etc), but I ended up with too much duplicated code. Same issue when swapping the order (365_mailbox, 365_group, etc).
So now I'm trying to implement abstractions based on behaviors to better share code when possible, and select style/strategy separately for versions that need a more unique approach. But the .Searches are having trouble accessing the .Retrievals.
I guess I could pass the retrievals as a parameter to the searches constructor like I am for config, but that approach doesn't scale well if I start ending up with more behavior categories with interlaced dependencies. Also if I can get the sibling access working I can stop passing config as a parameter and just access them directly, which should clean up the code a bit more.
Below is a reduced example for reference (yes, despite it's complexity it's very reduced, the final version has around 26 classes last time I counted)
Class Mail_Factory
{
static [Mail_Interface] BuildExample1()
{
$mail_object = "Pretend 365 group"
return [Mail_Factory]::Build($mail_object)
}
static [Mail_Interface] BuildExample2()
{
$mail_object = "Pretend Local Group"
return [Mail_Factory]::Build($mail_object)
}
static [Mail_Interface] Build($mail_object)
{
[Mail_Interface] $interface = [Mail_Interface]::new()
$interface.config = [Mail_Config]::new($mail_object)
$interface.retrieve = [Mail_Retrieve]::new($interface.config)
$interface.search = [Mail_Search]::new($interface.config)
[Mail_Retrieve_Members_Style] $members_style = switch ($mail_object)
{
("Pretend 365 group") { [Mail_Retrieve_Members_365Group]::new($interface.config) }
("Pretend Local Group") { [Mail_Retrieve_Members_LocalGroup]::new($interface.config) }
}
# notice that we're setting a specific retreival strategy for "members" depending on object type
$interface.config.members_style = $members_style
return $interface
}
}
Class Mail_Interface
{
Mail_Interface(){}
[Mail_Config] $config
[Mail_Retrieve] $retrieve # This is a facade to unify the way we call different kinds of retreivals (directly from settings, derived from settings, shared based on type, or unique to a specific combination of settings)
[Mail_Search] $search # This is a facade for the same reasons as $retreive
[bool] has_member($search) {return $this.search.has_member($search)}
[object] members() {return #($this.retrieve.members())}
}
Class Mail_Config
{
Mail_Config($mail_object) {$this.mail_object = $mail_object}
[Mail_Retrieve_Members_Style] $members_style # set by factory
[object] $mail_object
}
Class Mail_Retrieve
{
Mail_Retrieve($config){$this.config = $config}
[Mail_Config] $config
[object] Members(){return $this.config.members_style.Members()}
}
Class Mail_Retrieve_Members_Style
{
Mail_Retrieve_Members_Style($config){$this.config = $config}
[Mail_Config] $config
[object] Members(){throw("Syle not yet selected")}
}
Class Mail_Retrieve_Members_365Group : Mail_Retrieve_Members_Style
{
Mail_Retrieve_Members_365Group($config) : base($config) {}
# inherited: [Mail_Config] $config
[object] Members(){return "member of 365 group"}
}
Class Mail_Retrieve_Members_LocalGroup : Mail_Retrieve_Members_Style
{
Mail_Retrieve_Members_LocalGroup($config) : base($config) {}
# inherited: [Mail_Config] $config
[object] Members(){return "member of local group"}
}
Class Mail_Search
{
Mail_Search($config){$this.config = $config}
[Mail_Config] $config
[bool] has_member($search)
{
$members = $this.parent.retrieve.members() # !!! Problem exists here !!!
if ($members -contains "$search") {return $true}
else {return $false}
}
}
function TestExample1()
{
# from
# $a.search.has_member()
# we're trying to access
# $a.retrieve.Members()
$a = [Mail_Factory]::BuildExample1()
$member_a = $a.members()[0]
if ($a.has_member($member_a))
{"Success!"}
else
{"Failed, member match incorrect"}
}
function TestExample2()
{
# from
# $b.search.has_member()
# we're trying to access
# $b.retrieve.Members()
$b = [Mail_Factory]::BuildExample2()
$member_b = $b.members()[0]
$b.has_member($member_b)
if ($b.has_member($member_b))
{"Success!"}
else
{"Failed, member match incorrect"}
}
$result_a = TestExample1
$result_b = TestExample2
$result_a
$result_b
This spits out the following error (I've marked the line with !!! Problem exists here !!!)
You cannot call a method on a null-valued expression.
At line:88 char:9
+ $members = $this.super.retrieve.members() # !!! Problem exist ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (:) [], RuntimeException
+ FullyQualifiedErrorId : InvokeMethodOnNull
I have full control over this project and currently I'm open to completely refactoring to a different approach if I'm going about this the wrong way. I'm comfortable using chained constructors especially if they improve readability.
And I've been exploring design patterns on G4G, but my experience with them is still neophytic
I ended up replacing the reference to Config with a reference to the parent (Interface). While this opens the possibility of accidental recursion, this appears to be the only way to do it (that I've yet found)
I also flattened the methods, removing .search and .retrieve as they turned out not to be needed for now.
Class Mail_Factory
{
static [Mail_Interface] BuildExample1()
{
$mail_object = "Pretend 365 group"
return [Mail_Factory]::Build($mail_object)
}
static [Mail_Interface] BuildExample2()
{
$mail_object = "Pretend Local Group"
return [Mail_Factory]::Build($mail_object)
}
static [Mail_Interface] Build($mail_object)
{
[Mail_Interface] $interface = [Mail_Interface]::new()
$interface.config = [Mail_Config]::new($mail_object)
#$interface.retrieve = [Mail_Retrieve]::new($interface)
#$interface.search = [Mail_Search]::new($interface)
[Mail_Members_Style] $members_style = switch ($mail_object)
{
("Pretend 365 group") { [Mail_Members_365Group]::new($interface) }
("Pretend Local Group") { [Mail_Members_LocalGroup]::new($interface) }
}
# notice that we're setting a specific retreival strategy for "members" depending on object type
$interface.members_style = $members_style
$interface.has_member_style = [Mail_HasMember_Default]::new($interface)
return $interface
}
}
Class Mail_Interface
{
Mail_Interface(){}
[Mail_Config] $config
# set by factory #
[Mail_HasMember_Style]$has_member_style
[Mail_Members_Style]$members_style
# set by factory #
[bool] HasMember($search) {return $this.has_member_style.HasMember($search)}
[object] members() {return #($this.members_style.Members())}
}
Class Mail_Config
{
Mail_Config($mail_object) {$this.mail_object = $mail_object}
# IRL we store a lot more in here
[object] $mail_object
}
Class Mail_Members_Style
{
Mail_Members_Style($interface){$this.interface = $interface}
[Mail_Interface] $interface
[object] Members(){throw("Syle not yet selected")}
}
Class Mail_Members_365Group : Mail_Members_Style
{
Mail_Members_365Group($interface) : base($interface) {}
# inherited: [Mail_Interface] $interface
[object] Members(){return "member of 365 group"}
}
Class Mail_Members_LocalGroup : Mail_Members_Style
{
Mail_Members_LocalGroup($interface) : base($interface) {}
# inherited: [Mail_Interface] $interface
[object] Members(){return "member of local group"}
}
Class Mail_HasMember_Style
{
Mail_HasMember_Style($interface){$this.interface = $interface}
[Mail_Interface] $interface
[bool] HasMember($search){throw("Syle not yet selected")}
}
Class Mail_HasMember_Default : Mail_HasMember_Style
{
Mail_HasMember_Default($interface) : base($interface) {}
# inherited: [Mail_Interface] $interface
[bool] HasMember($search)
{
$members = $this.interface.members()
if ($members -contains "$search") {return $true}
else {return $false}
}
}
function TestExample1()
{
# from
# $a.has_member_style.has_member()
# we're trying to access
# $a.members_style.Members()
$a = [Mail_Factory]::BuildExample1()
$member_a = $a.members()[0]
if ($a.HasMember($member_a))
{"Success!"}
else
{"Failed, member match incorrect"}
}
function TestExample2()
{
# from
# $b.has_member_style.hasmember()
# we're trying to access
# $b.members_style.Members()
$b = [Mail_Factory]::BuildExample2()
$member_b = $b.members()[0]
if ($b.HasMember($member_b))
{"Success!"}
else
{"Failed, member match incorrect"}
}
function TestExample3()
{
# Verifying HasMember is actually checking stuff
$b = [Mail_Factory]::BuildExample1()
if ($b.HasMember("Not A Member"))
{"Failed, HasMember is incorrectly returning true"}
else
{"Success!"}
}
$result_a = TestExample1
$result_b = TestExample2
$result_c = TestExample2
$result_a
$result_b
$result_c
And the test results
Success!
Success!
Success!

DSC for initialising and formatting disks

I need to format a disk for servers using DSC. I tried using the below from
https://blogs.msdn.microsoft.com/timomta/2016/04/23/how-to-use-powershell-dsc-to-prepare-a-data-drive-on-an-azure-vm/#comment-1865
But it doesnt work as it doesn't seem to be complete, I get errors
"+ xWaitforDisk Disk2 + ~~~~~~~~~~~~ Resource 'xWaitForDisk' requires
that a value of type 'String' be provided for property 'DiskId'.
At line:18 char:1 + DiskNumber = 2 + ~~~~~~~~~~ The member
'DiskNumber' is not valid. Valid members are 'DependsOn', 'DiskId',
'DiskIdType', 'PsDscRunAsCredential', 'RetryCount',
'RetryIntervalSec'. "
Configuration DataDisk
{
Import-DSCResource -ModuleName xStorage
Node localhost
{
xWaitforDisk Disk2
{
DiskNumber = 2
RetryIntervalSec = 60
Count = 60
}
xDisk FVolume
{
DiskNumber = 2
DriveLetter = 'F'
FSLabel = 'Data'
}
}
You need to replace DiskNumber with DiskID.
Take a look at the examples on GitHub https://github.com/PowerShell/StorageDsc/tree/dev/Modules/StorageDsc/Examples/Resources
You can find the DiskId with powershell use the command: Get-Disk

How do I instantiate CorRuntimeHost from mscoree.tlb in PowerShell?

I want to enumerate all the AppDomains in the current process from PowerShell. The process happens to be Visual Studio, which is hosting StudioShell. To do that I need to instantiate CorRuntimHost, which is part of mscoree.tlb, so I can adapt this C# code..
I tried to get the proper name of CorRunTimeHost and pass it to New-Object -COMObject "objectName". Based on this forum posting, I searched the registry and I think the correct name is CLRMetaData.CorRuntimeHost. However, while New-Object -ComObject 'CLRMetaData.CorRuntimeHost' -Strict does return an object, it only exposes the methods intrinsic to a COM object.
Based on this stackoverflow question I tried [Activator]::CreateInstance(). However, the following two statements give me the same problem as New-Object, namely I can't call the ICorRuntimeHost::EnumDomains() method.
$corRuntimeHost = [Activator]::CreateInstance([Type]::GetTypeFromProgID('CLRMetaData.CorRuntimeHost'));
$enumerator = $null;
$corRuntimeHost.EnumDomains([ref]$enumerator);
Method invocation failed because [System.__ComObject] doesn't contain a method named 'EnumDomains'.
At line:1 char:1
+ $corRuntimeHost.EnumDomains([ref]$enumerator)
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (:) [], RuntimeException
+ FullyQualifiedErrorId : MethodNotFound
To get it working in PowerShell 3.0 I ended up having to use an AssemblyBuilder. Below is the working code:
The problem seems to be that there is no public constructor for mscoree.CorRuntimeHostClass in .NET 4.0 but there is in 3.5.
I later tested this on a Windows 7 VM with powershell 2.0 and now this code will work in PowerShell 2.0 and 3.0.
$tlbName = Split-Path -Parent ([AppDomain]::CurrentDomain.GetAssemblies() | Where { $_.Location -Match '\\mscorlib.dll$' }).Location
$tlbName = Join-Path $tlbName 'mscoree.tlb';
$csharpString = #"
//adapted from here http://blog.semanticsworks.com/2008/04/enumerating-appdomains.html
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.InteropServices;
public class ListProcessAppDomains
{
[DllImport( `"oleaut32.dll`", CharSet = CharSet.Unicode, PreserveSig = false )]
private static extern void LoadTypeLibEx
(String strTypeLibName, RegKind regKind,
[MarshalAs( UnmanagedType.Interface )] out Object typeLib);
private enum RegKind
{
Default = 0,
Register = 1,
None = 2
}
private class ConversionEventHandler : ITypeLibImporterNotifySink
{
public void ReportEvent( ImporterEventKind eventKind, int eventCode, string eventMsg )
{
Console.Error.WriteLine("Kind: {0} Code: {1} Message");
}
public Assembly ResolveRef( object typeLib )
{
string stackTrace = System.Environment.StackTrace;
Console.WriteLine("ResolveRef ({0})", typeLib);
Console.WriteLine(stackTrace);
return null;
}
}
public static AssemblyBuilder LoadMsCoreeDll( ref Object typeLib ) {
ConversionEventHandler eventHandler = new ConversionEventHandler();
string assemblyName = "PoshComWrapper.dll";
LoadTypeLibEx( #"$($tlbName)", RegKind.None, out typeLib );
TypeLibConverter typeLibConverter = new TypeLibConverter();
return typeLibConverter.ConvertTypeLibToAssembly( typeLib, assemblyName, 0, eventHandler, null, null, null, null );
}
}
"#
# So we can run this scipt multiple times
try { [ListProcessAppDomains] } catch { Add-Type -TypeDefinition $csharpString }
function Get-AppDomain {
$typeLib = $null;
$assemblyBuilder = [ListProcessAppDomains]::LoadMsCoreeDll([ref] $typeLib)
$corRuntimeHostClass = $assemblyBuilder.CreateInstance('PoshComWrapper.CorRuntimeHostClass')
$enumHandle = [IntPtr]::Zero
$corRuntimeHostClass.EnumDomains([ref] $enumHandle);
$appDomain = $null;
do
{
$corRuntimeHostClass.NextDomain($enumHandle, [ref] $appDomain);
if ($appDomain -ne $null -and $appDomain.GetType() -eq [AppDomain]) { $appDomain; }
} while ($appDomain -ne $null)
}
Get-AppDomain

How can I get PowerShell Added-Types to use Added Types

I'm working on a PoSh project that generates CSharp code, and then Add-Types it into memory.
The new types use existing types in an on disk DLL, which is loaded via Add-Type.
All is well and good untill I actualy try to invoke methods on the new types. Here's an example of what I'm doing:
$PWD = "."
rm -Force $PWD\TestClassOne*
$code = "
namespace TEST{
public class TestClassOne
{
public int DoNothing()
{
return 1;
}
}
}"
$code | Out-File tcone.cs
Add-Type -OutputAssembly $PWD\TestClassOne.dll -OutputType Library -Path $PWD\tcone.cs
Add-Type -Path $PWD\TestClassOne.dll
$a = New-Object TEST.TestClassOne
"Using TestClassOne"
$a.DoNothing()
"Compiling TestClassTwo"
Add-Type -Language CSharpVersion3 -TypeDefinition "
namespace TEST{
public class TestClassTwo
{
public int CallTestClassOne()
{
var a = new TEST.TestClassOne();
return a.DoNothing();
}
}
}" -ReferencedAssemblies $PWD\TestClassOne.dll
"OK"
$b = New-Object TEST.TestClassTwo
"Using TestClassTwo"
$b.CallTestClassOne()
Running the above script gives the following error on the last line:
Exception calling "CallTestClassOne" with "0" argument(s):
"Could not load file or assembly 'TestClassOne,...'
or one of its dependencies. The system cannot find the file specified."
At AddTypeTest.ps1:39 char:20
+ $b.CallTestClassOne <<<< ()
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : DotNetMethodException
What am I doing wrong?
This happens because any assemblies are looked for by the CLR loader in the application's (PowerShell's) base directory. Of course, it doesn't find your assembly there. The best way to solve this is to hook the AssemblyResolve event as stej mentions but use it to tell the CLR where the assembly is. You can't do this with PowerShell 2.0's Register-ObjectEvent because it doesn't work with events that require a return value (ie the assembly). In this case, let's use more C# via Add-Type to do this work for us. This snippet of code works:
ri .\TestClassOne.dll -for -ea 0
$resolver = #'
using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
namespace Utils
{
public static class AssemblyResolver
{
private static Dictionary<string, string> _assemblies;
static AssemblyResolver()
{
var comparer = StringComparer.CurrentCultureIgnoreCase;
_assemblies = new Dictionary<string,string>(comparer);
AppDomain.CurrentDomain.AssemblyResolve += ResolveHandler;
}
public static void AddAssemblyLocation(string path)
{
// This should be made threadsafe for production use
string name = Path.GetFileNameWithoutExtension(path);
_assemblies.Add(name, path);
}
private static Assembly ResolveHandler(object sender,
ResolveEventArgs args)
{
var assemblyName = new AssemblyName(args.Name);
if (_assemblies.ContainsKey(assemblyName.Name))
{
return Assembly.LoadFrom(_assemblies[assemblyName.Name]);
}
return null;
}
}
}
'#
Add-Type -TypeDefinition $resolver -Language CSharpVersion3
$code = #'
namespace TEST {
public class TestClassOne {
public int DoNothing() {
return 1;
}
}
}
'#
$code | Out-File tcone.cs
Add-Type -OutputAssembly TestClassOne.dll -OutputType Library -Path tcone.cs
# This is the key, register this assembly's location with our resolver utility
[Utils.AssemblyResolver]::AddAssemblyLocation("$pwd\TestClassOne.dll")
Add-Type -Language CSharpVersion3 `
-ReferencedAssemblies "$pwd\TestClassOne.dll" `
-TypeDefinition #'
namespace TEST {
public class TestClassTwo {
public int CallTestClassOne() {
var a = new TEST.TestClassOne();
return a.DoNothing();
}
}
}
'#
$b = new-object Test.TestClassTwo
$b.CallTestClassOne()
When you output the TestClassTwo to a dll (in the same directory as TestClassOne) and Add-Type it, it works. Or at least at my machine ;) So that's the ugly workaround.
When calling $b.CallTestClassOne() PowerShell tries (from some reason I don't know) to find assembly TestClassOne.dll at these locations:
LOG: Pokus o stažení nové adresy URL file:///C:/Windows/SysWOW64/WindowsPowerShell/v1.0/TestClassOne.DLL
LOG: Pokus o stažení nové adresy URL file:///C:/Windows/SysWOW64/WindowsPowerShell/v1.0/TestClassOne/TestClassOne.DLL
LOG: Pokus o stažení nové adresy URL file:///C:/Windows/SysWOW64/WindowsPowerShell/v1.0/TestClassOne.EXE
LOG: Pokus o stažení nové adresy URL file:///C:/Windows/SysWOW64/WindowsPowerShell/v1.0/TestClassOne/TestClassOne.EXE
This is output from fuslogvw tool. It might be useful for you. The same list of paths can bee seen live using ProcessMonitor.
You might also try this (before calling CallTestClassOne()
[appdomain]::CurrentDomain.add_assemblyResolve({
$global:x = $args
})
$b.CallTestClassOne()
$x | fl
This will show you what assembly failed and some more info.
I agree that it should work as you expect. So that's why this looks somewhat buggy.