Question:
Below is the exact code that I am having trouble with.
A brief description:
I am trying to set up 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 can’t find the type name “System.Printing.PrintServer”, or any other explicitly listed types.
After that failed, I moved it to its 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:
1 2 3 4 5 6 7 |
[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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 |
[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 } } |
Answer:
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:
1 2 3 4 5 |
Add-Type -AssemblyName System.Printing Add-Type -AssemblyName ReachFramework . $PSScriptRoot\Class.ps1 |
Class.ps1:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
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 } '@ |