Using .Net Objects within a Powershell (V5) Class

powershell load net dll
powershell list dll methods
powershell unable to find type

This is a major edit done for clarity purposes. It appears I need to work on forming thoughts. Below is the exact code that I am having trouble with. A brief description: I am trying to setup a powershell class that will hold objects of different types for easy access. I've done this numerous times in C#, so I thought it would be fairly straight forward. The types wanted are [System.Printing] and WMI-Objects.

Originally I had tried to write the class directly to my Powershell profile for easy usage, but my profile fails to load when I have to class code in it. Saying that it cant find the type name "System.Printing.PrintServer", or any other explicitly listed types. After that failed I moved it to it's own specific module and then set my profile to import the module on open. However, even when stored in its own module, if I explicitly list a .Net Type for any of the properties, the entire module fails to load. Regardless of whether I have added or imported the type / dll. The specific problem area is this: [string]$Name [System.Printing.PrintServer]$Server [System.Printing.PrintQueue]$Queue [System.Printing.PrintTicket]$Ticket [System.Management.ManagementObject]$Unit [bool]$IsDefault

When I have it set to this, everything "kind of" works, but then all my properties have the _Object type, which is not helpful. [string]$Name $Server $Queue $Ticket $Unit $IsDefault

Add-Type -AssemblyName System.Printing
Add-Type -AssemblyName ReachFramework
Class PrinterObject
{
    [string]$Name
    [System.Printing.PrintServer]$Server
    [System.Printing.PrintQueue]$Queue
    [System.Printing.PrintTicket]$Ticket
    [System.Management.ManagementObject]$Unit
    [bool]$IsDefault

   PrinterObject([string]$Name)
    {
        #Add-Type -AssemblyName System.Printing
        #Add-Type -AssemblyName ReachFramework
        $this.Server = New-Object System.Printing.PrintServer -ArgumentList [System.Printing.PrintSystemDesiredAccess]::AdministrateServer
        $this.Queue =  New-Object System.Printing.PrintQueue (($this.Server), ($this.Server.GetPrintQueues() | 
        Where-Object {$_.Name -match $Name} | Select-Object -ExpandProperty Name))

        $this.Ticket = $this.Queue.UserPrintTicket
        $this.Unit = Get-WmiObject -Query "SELECT * FROM Win32_Printer WHERE Name LIKE `"%$Name%`""
    }

    PrinterObject([string]$Name, [bool]$IsNetwork)
    {
        #Add-Type -AssemblyName System.Printing
        #Add-Type -AssemblyName ReachFramework
        if($IsNetwork -eq $true) {
        $this.Server = New-Object System.Printing.PrintServer ("\\Server")
        $this.Queue =  New-Object System.Printing.PrintQueue (($this.Server), ($this.Server.GetPrintQueues() | 
        Where-Object {$_.Name -match $Name} | Select-Object -ExpandProperty Name))

        $this.Ticket = $this.Queue.UserPrintTicket
        $this.Unit = Get-WmiObject -Query "SELECT * FROM Win32_Printer WHERE Name LIKE `"%$Name%`""
        }
        else {
        $This.Server = New-Object System.Printing.PrintServer -argumentList [System.Printing.PrintSystemDesiredAccess]::AdministrateServer
        $this.Queue =  New-Object System.Printing.PrintQueue (($this.Server), ($this.Server.GetPrintQueues() | 
        Where-Object {$_.Name -match $Name} | Select-Object -ExpandProperty Name))

        $this.Ticket = $this.Queue.UserPrintTicket
        $this.Unit = Get-WmiObject -Query "SELECT * FROM Win32_Printer WHERE Name LIKE `"%$Name%`"" }
    }
    [void]SetPrintTicket([int]$Copies, [string]$Collation, [string]$Duplex)
    {
        $this.Ticket.CopyCount = $Copies
        $this.Ticket.Collation = $Collation
        $this.Ticket.Duplexing = $Duplex
        $this.Queue.Commit()
    }

    [Object]GetJobs($Option)
    {
            if($Option -eq 1) { return $this.Queue.GetPrintJobInfoCollection() | Sort-Object -Property JobIdentifier | Select-Object -First 1}
            else { return $this.Queue.GetPrintJobInfoCollection() }
    }
    static [Object]ShowAllPrinters()
    {
        Return Get-WmiObject -Class Win32_Printer | Select-Object -Property Name, SystemName
    }

}

Every PowerShell script is completely parsed before the first statement in the script is executed. An unresolvable type name token inside a class definition is considered a parse error. To solve your problem, you have to load your types before the class definition is parsed, so the class definition has to be in a separate file. For example:

Main.ps1:

Add-Type -AssemblyName System.Printing
Add-Type -AssemblyName ReachFramework

. $PSScriptRoot\Class.ps1

Class.ps1:

using namespace System.Management
using namespace System.Printing

Class PrinterObject
{
    [string]$Name
    [PrintServer]$Server
    [PrintQueue]$Queue
    [PrintTicket]$Ticket
    [ManagementObject]$Unit
    [bool]$IsDefault
}

The other possibility would be embed Class.ps1 as a string and use Invoke-Expression to execute it. This will delay parsing of class definition to time where types is available.

Add-Type -AssemblyName System.Printing
Add-Type -AssemblyName ReachFramework

Invoke-Expression @‘
    using namespace System.Management
    using namespace System.Printing

    Class PrinterObject
    {
        [string]$Name
        [PrintServer]$Server
        [PrintQueue]$Queue
        [PrintTicket]$Ticket
        [ManagementObject]$Unit
        [bool]$IsDefault
    }
’@

Implementing a .NET Class in PowerShell v5, Every PowerShell script is completely parsed before the first statement in the script is executed. An unresolvable type name token inside a class definition is  Before the new class-building syntax existed, if you wanted to build custom objects in PowerShell, you generally would either: 1) use the [PSCustomObject] type, or 2) build a .NET class in C#, and use the Add-Type command to import it into the PowerShell session. Drinking beer with #PowerShell v5 classes.

To complement PetSerAl's helpful answer:

using assembly should be the right solution, but its use at parse time hasn't been implemented yet as of Windows PowerShell v5.1 / PowerShell Core v6.1, because it requires extra work to avoid the potential for undesired execution of arbitrary code when an assembly is loaded.

Implementing this has been green-lighted in this GitHub issue, and the necessary work is being tracked as part of this issue.

Useful .NET classes for PowerShell – 4sysops, Creating a Class. Classes basically allow us to define custom object types, and the behaviors that we use to interact with them. For example, you  Anyone here can make use of .NET and not be a developer in PowerShell. In fact, you are probably using it right now and not even aware of it! An easy example of using a static method within a .NET class is with System.Math class and using the method Sqrt to determine the square root of a number.

A better solution (than just invoking the entire class in a string) would be to just create your objects and pass them to the class as parameters. For example, this runs fine:

Add-Type -AssemblyName PresentationCore,PresentationFramework

class ExampleClass {
    $object

    ExampleClass ($anotherClass) {
        $this.object = $anotherClass
    }

    [void] Show () {
        $this.object::Show('Hello')
    }
}

$y = [ExampleClass]::new([System.Windows.MessageBox])
$y.Show()

However, if you were to do something like this, you can expect Unable to find type [System.Windows.MessageBox].

Add-Type -AssemblyName PresentationCore,PresentationFramework

class ExampleClass2 {
    $object

    ExampleClass () {
        $this.object = [System.Windows.MessageBox]
    }

    [void] Show () {
        $this.object::Show('Hello')
    }
}

Creating Custom Types using PowerShell Classes, NET framework provides a class library you can use in your PowerShell scripts. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. #1: $my10MBFile = New-Object  Using .Net Objects within a Powershell (V5) Class (3 answers) Closed 2 years ago. The following code runs fine when executed in ISE: (or create the classes using

Why Use .NET Framework Classes from Within PowerShell , Because both commands return a DateTime .NET Framework class object, there is no advantage to the first command. Some may ask, what does  Summary: Microsoft Scripting Guy, Ed Wilson, talks about the need to use .NET Framework classes from within Windows PowerShell code. Microsoft Scripting Guy, Ed Wilson, is here. The Scripting Wife and I had a great meeting with the Windows PowerShell Users Group in Charlotte, North Carolina. It was a script club format, so there was no set agenda, nor was there a formal presentation.

Using .NET Members in PowerShell -- Microsoft Certified , NET classes to build out an object as well as using methods in the objects to perform If you are running PowerShell V5, then you are in luck! Another way to make sure that a .NET Framework class is available is to use the GetAssembly static method from the System.Reflection.Assembly .NET Framework class. The namespace is System.Reflection, and the class name is Assembly. Static methods are always available from the class.

Beyond custom objects: Create a .NET class, NET object that I can create, manage, and use in many different contexts. In this post, I'll use the new class feature of the Windows PowerShell 5.0  This post is based on the September 2014 preview release of WMF 5.0. This is pre-release software, so this information may change. One of the banner new features in PowerShell v5 is support for real live .NET Framework class creation in Windows PowerShell.

Comments
  • Can you be concrete/literal with "the types specified" ? The pseudo code you give means nothing. You may not be separating the namespace, class and value parts properly. No one can tell that from [System.Object.SomeDotNetObject]::Enum .
  • Where problem type belong? Is it in standard .NET assembly, custom assembly in GAC, custom assembly loaded by path or dynamic assembly Add-Type -TypeDefinition ...?
  • @PetSerAl Doesn't really matter - if the namespace/type can be resolved outside the class definition, why shouldn't that also apply inside the class? Sounds like a bug to me.
  • @MathiasR.Jessen I can not reproduce that behavior on my PC, so I ask for additional details to check if I missing something.
  • @MartinMaat I have updated with the exact code for the class that I am using. I feel like this may be an issue with types not loading properly into powershell, because if I manually load the types then manually type this class into the shell, it works perfectly fine
  • So. . . I tried this and I guess a very weird bug just caused an infinite amount of powershell windows to pop up so fast that my system ran out of memory. . .
  • You dot-sourced the file the dot-source statement is in? Like putting ". $PSScriptRoot\Class.ps1" in a file named "Class.ps1" ?
  • So is this behaviour a bug, or is PowerShell's import system this terrible?
  • @tyteen4a03 using assembly do not cause parse time assembly loading. Assembly loading can cause arbitrary code to be executed, which can be undesired. And extracting types from assembly metadata without loading it not yet implemented, AFAIK.
  • @TNT How is eval over constant string is more dangerous, then execution of arbitrary script file?