# encoding: utf-8 # api: powershell # title: Wpf with powershell # description: I know there already are a bunch of approaches to this, but this is my somewhat hacky contribution to wpf with powershell. I wanted to be able to draw my ui in visual studio or blend and then use the xaml with my powershell script in a simple way. It certainly has room for improvements, but I think it has potential. I’m quite sure that there are other (nicer) ways to do some of the more hacky stuff. # version: 0.1 # type: function # author: David Sjstrand # license: CC0 # function: Get-Runspace # x-poshcode-id: 5454 # x-archived: 2014-09-25T15:57:06 # x-published: 2014-09-19T07:17:00 # # So this is how to use this: # 1. draw your ui in visual studio or blend (c# wpf project). Be sure to name all controls and assign handlers for the events you want to use (don’t implement them, just name them). # 2. copy the .xaml file and wpftools.ps1 (the script below) to your script project folder # 3. dot-source wpftools.ps1 # 4. run Get-WpfSnippet -xamlPath > myScript.ps1 # 5. open myScript.ps1 and implement the functions (there will be a function for each event handler you assigned) # 6. run myScript.ps1 (enjoy) # #Requires -Version 2 Add-Type -Assembly PresentationFramework Add-Type -Assembly PresentationCore #Add-Type –AssemblyName WindowsBase function Get-Runspace ($ScriptPath) { if ($runspaceCreated -or [System.Management.Automation.Runspaces.Runspace]::DefaultRunspace.apartmentstate -eq "STA") { Write-Debug "No new runspace was created" return } if ($PSBoundParameters.ContainsKey('ScriptPath')) { $ScriptPath = Resolve-Path $ScriptPath } elseif ($host.version.major -ge 3) { $ScriptPath = $MyInvocation.PSCommandPath } else { $ScriptPath = Resolve-Path (Get-PSCallStack)[-2].InvocationInfo.InvocationName } Write-Debug "Script path: $ScriptPath" Write-Debug "Creating a new STA runspace ..." # Create a new runspace $rs = [Management.Automation.Runspaces.RunspaceFactory]::CreateRunspace($Host) $rs.ApartmentState = "STA" $rs.ThreadOptions = “ReuseThread” $rs.Open() $rs.SessionStateProxy.SetVariable("runspaceCreated",$true) $rs.SessionStateProxy.SetVariable("debugPreference",$debugPreference) # Rerun the script in the new apartment $psCmd = [System.Management.Automation.PowerShell]::Create() $psCmd.Runspace = $rs Write-Debug "Rerunning $ScriptPath" [void]$psCmd.AddCommand("Set-Location") [void]$pscmd.AddParameter("Path",(Get-Location).Path) [void]$psCmd.AddScript($ScriptPath) [void]$psCmd.Invoke() $rs.Close() exit } function Get-WindowsClasses { $exportedClasses = [System.Reflection.Assembly]::GetAssembly([System.Windows.Window]).exportedTypes $exportedControlClasses = $exportedClasses | Where-Object {$_.isclass -and $_.fullname -like "System.Windows.*"} $controlClasses = @{} foreach ($controlClass in $exportedControlClasses) { $controlClasses[$controlClass.Name] = $controlClass.FullName } $controlClasses } function Show-WpfWindow ([string]$XamlPath=".\MainWindow.xaml", [string]$HashTableName) { if (!(Test-Path $XamlPath)) { throw "Could not find file $XamlPath" } [xml]$xaml = Get-Content $XamlPath $nsmgr = new-object system.xml.xmlnamespacemanager($xaml.nametable) $nsmgr.AddNamespace("x",$xaml.DocumentElement.x) $xaml.window.RemoveAttribute("x:Class") $controlEvents = @{} $controlClasses = Get-WindowsClasses :outer foreach ($element in $xaml.SelectNodes('//*[@x:Name]', $nsmgr)) { $name = $element.name $typename = $controlClasses[$element.LocalName] $type = $null try { $type = [type]$typename } catch { Write-Debug "type $typename does not exist"; continue outer} Write-Debug "$typeName`: $name ($(@($type.GetEvents()).count))" foreach ($event in $type.GetEvents()) { $attributeName = $event.Name $attributeValue = $element.GetAttribute($attributeName) if ($attributeValue -and (Test-Path "function:$attributeValue")) { Write-Debug "Found event handler: $attributeName - $attributeValue" $controlEvents[$name] += @{$attributeName=$attributeValue} } elseif (Test-Path "function:${name}_$attributeName") { Write-Debug "Found eventhandler $name_$attributeName" $controlEvents[$name] += @{$attributeName="${name}_$attributeName"} } if ($AttributeValue) { $element.RemoveAttribute($attributeName) } } } $reader = New-Object System.Xml.XmlNodeReader($xaml) $Window = [Windows.Markup.XamlReader]::Load( $reader ) if ($PSBoundParameters.ContainsKey("HashTableName")) { if (Test-Path "Variable:$HashTableName") { Remove-Variable $HashTableName } $HashTable = (new-variable -name $HashTableName -value @{} -PassThru -Option Constant).Value } foreach ($element in $xaml.SelectNodes('//*[@x:Name]', $nsmgr)) { $name = $element.name $control = $Window.FindName($name) if ($control) { if ($PSBoundParameters.ContainsKey("HashTableName")) { $HashTable[$name] = $control } else { if (Test-Path "Variable:$name") { Remove-Variable $name } New-Variable -Name $name -Value $control -Option Constant -ErrorAction SilentlyContinue } } } foreach ($controlName in $controlEvents.Keys) { $control = $Window.FindName($controlName) if (!$control) { continue } foreach ($eventName in $controlEvents[$controlName].Keys) { $scriptBlock = [System.Management.Automation.ScriptBlock]::Create($controlEvents[$controlName][$eventName]) Write-Debug "$controlname.add_$eventName" ($control."add_$eventName").Invoke($scriptBlock) } } [void]$Window.ShowDialog() } function Get-WpfSnippet ($XamlPath=".\MainWindow.xaml", [string]$HashTableName, [parameter()] [ValidateSet("None", "Normal", "High", "Full")] [String[]] $CommentDetail="Normal", $CommentBorderLength = 85) { if ([System.Management.Automation.Runspaces.Runspace]::DefaultRunspace.apartmentstate -ne "STA") { throw "Get-WpfSnippet must be run in a single threaded apartment. Start PowerShell with the -STA flag and rerun the script." exit } [xml]$xaml = Get-Content $XamlPath $nsmgr = new-object system.xml.xmlnamespacemanager($xaml.nametable) $nsmgr.AddNamespace('x',$xaml.DocumentElement.x) #$xaml.window.RemoveAttribute("x:Class") if ($CommentDetail -ne 'None') { '#' * $CommentBorderLength '# Controls:' '#' } $controlEvents = @{} $controlTypes = @{} $controlClasses = Get-WindowsClasses :outer foreach ($element in $xaml.SelectNodes('//*[@x:Name]', $nsmgr)) { $name = $element.name $typename = $controlClasses[$element.LocalName] Write-Debug "$typeName`: $name" $type = $null if ($controlTypes.ContainsKey($typename)) { $type = $controlTypes[$typename] } else { try { $type = [type]$typename } catch { Write-Debug "Unknown error getting type $typename"; continue outer} $controlTypes[$typename] = $type } if ($CommentDetail -ne 'None') { if ($PSBoundParameters.ContainsKey("HashTableName")) { '# ${0}{1,-20} ({2})' -F $HashTableName,"['$name']",$typename } else { '# ${0,-20} ({1})' -F $name,$typename } } foreach ($event in $type.GetEvents()) { $attributeName = $event.Name $attributeValue = $element.GetAttribute($attributeName) if ($attributeValue) { Write-Debug "Found event handler: $attributeName - $attributeValue" $controlEvents[$attributeValue] += @{$name=$attributeName} #$element.RemoveAttribute($attributeName) } } } if ("High","Full" -contains $CommentDetail) { '#' * $CommentBorderLength '# Types:' '#' foreach ($typename in $controlTypes.Keys) { "# $typename" $str = '# Events:' $count = 0 foreach ($eventName in ($controlTypes[$typename].GetEvents() | Select-Object -ExpandProperty Name)) { if (!($count++ % 3)) { $str += "`n# " } $str += "$eventName ".PadRight(30) } $str.Split("`n") if ($CommentDetail -ne "Full") { continue } "# Properties:" foreach ($Property in $controlTypes[$typename].GetProperties()) { '# {0,-30} ({1})' -F $property.name,$property.propertyType.fullname } } } if ($CommentDetail -ne 'None') { '#' '#' * $CommentBorderLength '' } '#Requires -Version 2' '. .\wpftools.ps1' 'Get-Runspace $MyInvocation.MyCommand.Definition' '' '# Event handlers:' '' foreach ($eventName in $controlEvents.Keys) { foreach ($controlName in $controlEvents[$eventName].Keys) { "# $controlName $($controlevents[$eventName][$controlName])" } "function $eventName" '{' '}' '' } if ($PSBoundParameters.ContainsKey("HashTableName")) { "Show-WpfWindow -XamlPath '$XamlPath' -HashTableName '$HashTableName'" } else { "Show-WpfWindow -XamlPath '$XamlPath'" } }