I'm trying to create a pack ui referencing a xaml resource inside of an assembly file in powershell. After reading this post I tried to do this:
$resource = new-object system.uri("pack://application:,,,/WPFResource;component/test.xaml")
The I get an error noting that it is expecting a port since there are two colons.
Can anyone please advice?
You can go about this one of two ways. One is to load up and init the WPF infrastructure:
Add-Type -AssemblyName PresentationFramework,PresentationCore
[windows.application]::current > $null # Inits the pack protocol
new-object system.uri("pack://application:,,,/WPFResource;component/test.xaml")
The other way is to manually register the pack protocol:
$opt = [GenericUriParserOptions]::GenericAuthority
$parser = new-object system.GenericUriParser $opt
if (![UriParser]::IsKnownScheme("pack")) {
[UriParser]::Register($parser,"pack",-1)
}
new-object system.uri("pack://application:,,,/WPFResource;component/test.xaml")
Related
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:
[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
}
'#
To complement PetSerAl's helpful answer, which explains the underlying problem and contains effective solutions, with additional background information:
To recap:
As of PowerShell 7.3.1, a PowerShell class definition can only reference .NET types that have already been loaded into the session before the script is invoked.
Because class definitions are processed at parse time of a script, rather than at runtime, Add-Type -AssemblyName calls inside a script execute too late for the referenced assemblies' types to be known to any class definitions inside the same script.
A using assembly statement should solve this problem, but currently doesn't:
using assembly should be the parse-time equivalent of an Add-Type (analogous to the relationship between using module and Import-Module), but this hasn't been implemented yet, because it requires extra work to avoid the potential for undesired execution of arbitrary code when an assembly is loaded.
Implementing a solution has been green-lighted in GitHub issue #3641, and the necessary work is being tracked as part of GitHub issue #6652 - but it is unclear when this will happen, given that the issue hasn't received attention in several years.
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')
}
}
I am wondering how to accomplish the following and hope you can help:
I want to execute several tasks on different computers by providing a array of computernames using
Invoke-Command -ComputerName $ComputerNameArray -ScriptBlock { ...}
Inside the scriptblock, I want to access the current ComputerName (not the whole array).
How is this possible?
I tried enclosing the Invoke-Command within a foreach loop, something like this:
foreach ($Computer in $ComputerNameArray)
{
Invoke-Command-ComputerName $Computer -ScriptBlock { ...}
}
which works, this way I can access the current ComputerName with $env:Computer but as the foreach isn't necessary here, I want to get rid of it.
Any ideas?
Thanks all!
This works for me:
Invoke-Command $ComputerNameArray { $env:computername }
Something that I can not understand while creating a GUI with PowerShell
Reappearance:
Add-Type -AssemblyName System.Windows.Forms
function Build-MainForm2 () {
$form = New-Object Windows.Forms.Form
# MenuStrip
$menuStrip = New-Object Windows.Forms.MenuStrip
$menuStrip.Dock = 'Top'
# MenuStripItem
$msFile = New-Object -TypeName Windows.Forms.ToolStripMenuItem -ArgumentList 'File'
# Build Form
$menuStrip.Items.Add($msFile)
$form.Controls.Add($menuStrip)
return $form
}
$mainForm = Build-MainForm2
$mainForm.showDialog()
When I run this script PowerShell ISE show me error like this
[System.Object[]]에 이름이 'showDialog'인 메서드가 없으므로 메서드를 호출하지 못했습니다.
위치 D:\ProgramData\stgr\TWT\win\TwT\src\wtf.ps1:21 문자:25
+ $mainForm.showDialog <<<< ()
+ CategoryInfo : InvalidOperation: (showDialog:String) [], RuntimeException
+ FullyQualifiedErrorId : MethodNotFound
What it means is that there is no method
But when I comment out the 15th line
$menuStrip.Items.Add($msFile) -> #$menuStrip.Items.Add($msFile)
The script works fine
Go back to the beginning. I was wondering about the return value of Build-MainForm2, and when I tried to print out the value, it was an array with 0 and a Form instance.
(0, [System.Windows.Forms.Form])
After all, you can make the script work by changing the final code as follows:
$mainForm[1].showDialog()
But I still do not know why the return value was converted to this array.
I wonder if there is a neat solution to this.
Enviroment: PowerShell ISE 2.0 (Windows 7)
The Add method on Items returns an integer. You are not handling this, so it's getting inserted into your variable as an array item.
You can fix this by redirecting that output:
$null = $menuStrip.Items.Add($msFile)
As title says, I need help to create an event listener. I've searched for other answers, but they don't really help me, and I'm new to programming so I am struggling with this.
The purpose of the script is to search for scripts on the local system, pull information about them, and then display it on a GUI interface. From the interface, you would select the different displayed scripts, and be able to run the selected script, get script notes from the corresponding text file for the script, and also open the file path in file explorer.
The first section of my code is this:
Add-Type –assemblyName PresentationFramework
Add-Type –assemblyName PresentationCore
Add-Type –assemblyName WindowsBase
[Xml]$xaml = (design code omitted)
$xmlNodeReader = New-Object System.Xml.XmlNodeReader($xaml)
$Window = [System.Windows.Markup.XamlReader]::Load($xmlNodeReader)
This is the base for the rest of my stuff, which I haven't been able to find much help with. The next section of code is the header to prepare to fill the columns.
$values = Get-ChildItem -Path C:\(Directory) -Filter *.ps1 -Recurse -ErrorAction SilentlyContinue -Force
$ListView = $Window.FindName("OutputList")
# outputlist is the name of the listview in the xaml #
(further down would be the foreach loop to create the columns and register the data)
$ListView.ItemsSource = $values
Past that, I have nothing. I can't figure out how to create the listener for the selected item on the ListView. If I can do that, I should be able to grab the data and be set.
From other questions, WPF listview has a SelectionChanged event, and from FoxDeploy's WPF GUIs in PowerShell series, Part IV, the way to add event listeners is like so:
$ListView.Add_SelectionChanged({
# code here
})
Where the {} is a PowerShell scriptblock
I'm playing with SMO and tried to use it to change the database owners to sa. The code is
# To simplify our discussing, let's say we have a function Get-SMOServer
$s = Get-SMOServer -Instance myserver\myinstance
$s.databases | ?{$_.owner -ne "sa"} | %{$_.setowner("sa", $true)}
At this point, when I check the database owners from SSMS, the owners were already changed. However, if I check it from $s.databases, I still got the old data, until I do something like:
$s.databases | %{$_.refresh()}
Then I can get the correct result from $s.databases.
I checked the SMO objects and found many of them have a refresh() function. My question is, should I call refresh() every time I modified some object? How to find all object types that have a refresh() member?
Thanks
You can look at the SMO assembly. I'm using SMO which ships with 2008 R2:
$assm = add-type -AssemblyName "Microsoft.SqlServer.Smo, Version=10.0.0.0, Culture=neutral, PublicKeyToken=89845dcd8080cc91" -EA Stop -PassThru
$assm | %{$hasRefreshMethod = $null; $hasRefreshMethod = $_.GetMethods() | ?{$_.name -eq "Refresh"}; new-object psobject -property #{Name=$_.Name; HasRefreshMethod=$($hasRefreshMethod -ne $null)}}