pi/Main - Kopie.ps1

1221 lines
45 KiB
PowerShell
Raw Permalink Normal View History

2024-10-04 09:16:38 +02:00
<#
Program: PoSH GUI Template
Modified Date: 2023-06-07
Author: Jeremy Crabtree <jcrabtree at nct911 org> / <jeremylc at gmail>
Purpose: Use this template to create new GUI tools.
Copyright 2023 NCT 9-1-1
#>
#CREATE HASHTABLE AND RUNSPACE FOR GUI
$WPFGui = [hashtable]::Synchronized(@{ })
$newRunspace = [runspacefactory]::CreateRunspace()
$newRunspace.ApartmentState = "STA"
$newRunspace.ThreadOptions = "UseNewThread"
$newRunspace.Open()
$newRunspace.SessionStateProxy.SetVariable("WPFGui", $WPFGui)
#Create master runspace andadd code
$psCmd = [System.Management.Automation.PowerShell]::Create().AddScript( {
# Add WPF and Windows Forms assemblies. This must be done inside the runspace that contains the primary program code.
try {
Add-Type -AssemblyName PresentationCore, PresentationFramework, WindowsBase, System.Drawing, system.windows.forms, System.Windows.Controls.Ribbon, System.DirectoryServices.AccountManagement
}
catch {
Throw 'Failed to load Windows Presentation Framework assemblies.'
}
try {
Add-Type -Name Win32Util -Namespace System -MemberDefinition @'
[DllImport("Kernel32.dll")]
public static extern IntPtr GetConsoleWindow();
[DllImport("User32.dll")]
public static extern bool ShowWindow(IntPtr hWnd, Int32 nCmdShow);
[DllImport("user32.dll", SetLastError = true)]
public static extern int GetWindowLong(IntPtr hWnd, int nIndex);
[DllImport("user32.dll")]
public static extern int SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong);
[DllImport("user32.dll")]
public static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags);
[DllImport("user32.dll")]
public static extern bool BringWindowToTop(IntPtr hWnd);
[DllImport("user32.dll")]
public static extern bool SwitchToThisWindow(IntPtr hWnd, bool fUnknown);
const UInt32 SWP_NOSIZE = 0x0001;
const UInt32 SWP_NOMOVE = 0x0002;
const UInt32 SWP_NOACTIVATE = 0x0010;
const UInt32 SWP_SHOWWINDOW = 0x0040;
static readonly IntPtr HWND_BOTTOM = new IntPtr(1);
static readonly IntPtr HWND_NOTOPMOST = new IntPtr(-2);
static readonly IntPtr HWND_TOP = new IntPtr(0);
static readonly IntPtr HWND_TOPMOST = new IntPtr(-1);
public static void SetBottom(IntPtr hWindow)
{
SetWindowPos(hWindow, HWND_BOTTOM, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE | SWP_NOACTIVATE | SWP_SHOWWINDOW);
}
public static void SetTop(IntPtr hWindow)
{
SetWindowPos(hWindow, HWND_TOP, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE | SWP_NOACTIVATE | SWP_SHOWWINDOW);
}
'@
}
catch {
Write-Verbose "Win32Util already defined"
}
#region Utility Functions
# This is the list of functions to add to the InitialSessionState that is used for all Asynchronus Runsspaces
$SessionFunctions = New-Object System.Collections.ArrayList
function Invoke-Async {
<#
.SYNOPSIS
Runs code, with variables, asynchronously
.DESCRIPTION
This function runs the given code in an asynchronous runspace.
This lets you process data in the background while leaving the UI responsive to input
.PARAMETER Code
The code to run in the runspace
.PARAMETER Variables
A hashtable containing variable names and values to pass into the runspace
.EXAMPLE
$AsyncParameters = @{
Variables = @{
Key1 = 'Value1'
Key2 = $SomeOtherVariable
}
Code = @{
Write-Host "Key1: $Key1`nKey2: $Key2"
}
}
Invoke-Async @AsyncParameters
.NOTES
It's more reliable to pass single values than copmlex objects due to the way PowerShell handles value/reference passing with objects
.INPUTS
Variables, Code
.OUTPUTS
None
#>
[CmdletBinding()]
param (
[Parameter(Mandatory = $true)]
[ScriptBlock]
$Code,
[Parameter(Mandatory = $false)]
[hashtable]
$Variables
)
# Add the above code to a runspace and execute it.
$PSinstance = [powershell]::Create() #| Out-File -Append -FilePath $LogFile
$PSinstance.Runspace = [runspacefactory]::CreateRunspace($InitialSessionState)
$PSinstance.Runspace.ApartmentState = "STA"
$PSinstance.Runspace.ThreadOptions = "UseNewThread"
$PSinstance.Runspace.Open()
if ($Variables) {
# Pass in the specified variables from $VariableList
$Variables.keys.ForEach({
$PSInstance.Runspace.SessionStateProxy.SetVariable($_, $Variables.$_)
})
}
$PSInstance.AddScript($Code)
$PSinstance.BeginInvoke()
$WPFGui.Error = $PSInstance.Streams.Error
}
$SessionFunctions.Add('Invoke-Async') | Out-Null
Function New-WPFDialog() {
<#
.SYNOPSIS
This neat little function is based on the one from Brian Posey's Article on Powershell GUIs
.DESCRIPTION
I re-factored it a bit to return the resulting XaML Reader and controls as a single, named collection.
.PARAMETER XamlData
XamlData - A string containing valid XaML data
.EXAMPLE
$MyForm = New-WPFDialog -XamlData $XaMLData
$MyForm.Exit.Add_Click({...})
$null = $MyForm.UI.Dispatcher.InvokeAsync{$MyForm.UI.ShowDialog()}.Wait()
.NOTES
Place additional notes here.
.LINK
http://www.windowsnetworking.com/articles-tutorials/netgeneral/building-powershell-gui-part2.html
.INPUTS
XamlData - A string containing valid XaML data
.OUTPUTS
a collection of WPF GUI objects.
#>
Param(
[Parameter(Mandatory = $True, HelpMessage = 'XaML Data defining a WPF <window>', Position = 1)]
[string]$XamlData,
[Parameter(Mandatory = $False, HelpMessage = 'XaML Data defining WPF <Window.Resources', Position = 2)]
[string]$Resources
#[Parameter(Mandatory = $True, HelpMessage = 'Synchroinize hashtable to hold UI elements', Position = 2)]
#[ref]$WPFGui
)
# Create an XML Object with the XaML data in it
[xml]$xmlWPF = $XamlData
#If a Resource Dictionary has been included, import and append it to our Window
if ( -not [System.String]::IsNullOrEmpty( $Resources )) {
[xml]$xmlResourceWPF = $Resources
Foreach ($ChildNode in $xmlResourceWPF.ResourceDictionary.ChildNodes) {
($ImportNode = $xmlWPF.ImportNode($ChildNode, $true)) | Out-Null
$xmlWPF.Window.'Window.Resources'.AppendChild($ImportNode) | Out-Null
}
}
# Create the XAML reader using a new XML node reader, UI is the only hard-coded object name here
$XaMLReader = New-Object System.Collections.Hashtable
$XaMLReader.Add('UI', ([Windows.Markup.XamlReader]::Load((new-object -TypeName System.Xml.XmlNodeReader -ArgumentList $xmlWPF)))) | Out-Null
# Create hooks to each named object in the XAML reader
$Elements = $xmlWPF.SelectNodes('//*[@Name]')
ForEach ( $Element in $Elements ) {
$VarName = $Element.Name
$VarValue = $XaMLReader.UI.FindName($Element.Name)
$XaMLReader.Add($VarName, $VarValue) | Out-Null
}
return $XaMLReader
}
$SessionFunctions.Add('New-WPFDialog') | Out-Null
Function Set-Blur () {
<#
.SYNOPSIS
Blurs the MainWindow
.DESCRIPTION
.PARAMETER On
Turn blur on
.PARAMETER Off
Turn blur off
.EXAMPLE
Set-Blur -On
.NOTES
.INPUTS
none
.OUTPUTS
None
#>
[CmdletBinding()]
param (
[Parameter(ParameterSetName = 'On')]
[switch]
$On,
[Parameter(ParameterSetName = 'Off')]
[switch]
$Off
)
Switch ($PSCmdlet.ParameterSetName) {
'On' {
$WPFGui.MainGrid.Effect.Radius = 10
}
'Off' {
$WPFGui.MainGrid.Effect.Radius = 0
}
}
}
$SessionFunctions.Add('Set-Blur') | Out-Null
function Copy-Object {
<#
.SYNOPSIS
Copies a PSObject
.DESCRIPTION
Copies an object to new memory. Sometimes you really need to duplicate an object, rather than just create a new pointer to it. This serializes an object then deserializes it to new memory as a brute force mechanism to copy it.
.PARAMETER InputObject
The Object to be copied
.EXAMPLE
$NewCopy = Copy-Object $OldObject
.NOTES
.INPUTS
An object
.OUTPUTS
A copy of an object
#>
param (
$InputObject
)
$SerialObject = [System.Management.Automation.PSSerializer]::Serialize($InputObject)
return [System.Management.Automation.PSSerializer]::Deserialize($SerialObject)
}
$SessionFunctions.Add('Copy-Object') | Out-Null
Function New-MessageDialog() {
<#
.SYNOPSIS
Displays a Windows 11 styled MEssage Dialog
.DESCRIPTION
This is a utility function to display a baisc information, error, or simple input window in a Windows 11 style.
.PARAMETER DialogTitle
'Dialog Title'
.PARAMETER H1
'Major Header'
.PARAMETER DialogText
'Message Text'
.PARAMETER CancelText
'Cancel Text'
.PARAMETER ConfirmText
'Confirm Text'
.PARAMETER Beep
'Plays sound if set'
.PARAMETER GetInput
'Shows input TextBox if set'
.PARAMETER IsError
'Shows error icon if set'
.PARAMETER IsAsync
'Process asynchronously when set'
.PARAMETER Owner'
'Owner Window, required when this is a child'
.EXAMPLE
$NewDialog = @{
DialogTitle = 'Example Dialog'
H1 = "This is a pop-up dialog"
DialogText = "Dialog text should go here"
ConfirmText = 'Continue'
GetInput = $false
Beep = $true
IsError = $true
Owner = $WPFGui.UI
}
$Dialog = New-MessageDialog @NewDialog
.NOTES
.INPUTS
None
.OUTPUTS
DialogResult, nd Text if requested
#>
Param(
[Parameter(Mandatory = $True, HelpMessage = 'Dialog Title', Position = 1)]
[string]$DialogTitle,
[Parameter(Mandatory = $True, HelpMessage = 'Major Header', Position = 2)]
[string]$H1,
[Parameter(Mandatory = $True, HelpMessage = 'Message Text', Position = 3)]
[string]$DialogText,
[Parameter(Mandatory = $false, HelpMessage = 'Cancel Text', Position = 4)]
[string]$CancelText = $null,
[Parameter(Mandatory = $True, HelpMessage = 'Confirm Text', Position = 5)]
[string]$ConfirmText,
[Parameter(Mandatory = $false, HelpMessage = 'Plays sound if set', Position = 6)]
[switch]$Beep,
[Parameter(Mandatory = $false, HelpMessage = 'Shows input TextBox if set', Position = 7)]
[switch]$GetInput,
[Parameter(Mandatory = $false, HelpMessage = 'Shows error icon if set', Position = 8)]
[switch]$IsError,
[Parameter(Mandatory = $false, HelpMessage = 'Process asynchronously when set', Position = 9)]
[switch]$IsAsync,
[Parameter(Mandatory = $true, HelpMessage = 'Owner Window, required when this is a child', Position = 10)]
[PSObject]$Owner
)
$file = $PSScriptRoot + ".\DialogPanel.xaml"
$xamlData = Get-Content $file -Raw
$Dialog = New-WPFDialog -XamlData $xamlData
if ($Owner) {
if ($IsAsync) {
$Owner.Dispatcher.invoke([action] {
$Dialog.UI.Owner = $Owner
})
}
else {
$Dialog.UI.Owner = $Owner
}
}
$Dialog.MainWindow.Title = $DialogTitle
$Dialog.DialogTitle.Text = $DialogTitle
$Dialog.H1.Text = $H1
$Dialog.DialogText.Text = $DialogText
if ($CancelText) {
$Dialog.CancelButton.Content = $CancelText
}
else {
$Dialog.CancelButton.Visibility = 'hidden'
}
$Dialog.ConfirmButton.Content = $ConfirmText
if ($IsError) {
$Dialog.ErrorIcon.Visibility = 'Visible'
$Dialog.DialogText.Margin = '8,8,0,0'
}
if ($GetInput) {
$Dialog.Input.Visibility = 'Visible'
}
$Dialog.Add('Result', [System.Windows.Forms.DialogResult]::Cancel) | Out-Null
$Dialog.ConfirmButton.add_Click( {
$Dialog.Result = [System.Windows.Forms.DialogResult]::OK
$Dialog.UI.Close()
})
$Dialog.CancelButton.Add_Click( {
$Dialog.Result = [System.Windows.Forms.DialogResult]::Cancel
$Dialog.UI.Close()
})
$Dialog.UI.add_ContentRendered( {
if ($Beep) {
[system.media.systemsounds]::Exclamation.play()
}
})
$null = $Dialog.UI.Dispatcher.InvokeAsync{ $Dialog.UI.ShowDialog() }.Wait()
return @{
DialogResult = $Dialog.Result
Text = $Dialog.Input.Text
}
}
$SessionFunctions.Add('New-MessageDialog') | Out-Null
function Write-Activity {
<#
.SYNOPSIS
Write a colorized entry into the specified RichTextBox
.DESCRIPTION
This is usually used to write an entry into a RichTexBox which is being used as an Activity Log.
.PARAMETER Prefix
The "prefix" text, usually program section or activity, to pre-pended to the row
.PARAMETER Text
The actual text to write
.PARAMETER Stream
The RichTextBox to write to
.PARAMETER IsError
If set, this is an error message, write everything in RED
.EXAMPLE
Write-Activity -Prefix 'PoSH GUI Template' -Text 'Example Activity Log Entry' -Stream 'Output'
.NOTES
If you change the name of $WPFGui, you'll need to change it here.
.INPUTS
Text
.OUTPUTS
None
#>
param (
# Prefix text, describe which part is printing output
[Parameter(Mandatory = $true)]
[string]
$Prefix,
# Text to be printed
[Parameter(Mandatory = $true)]
[string]
$Text,
# Output stream to be used
[Parameter(Mandatory = $true)]
[string]
$Stream,
[switch]
$IsError
)
$WPFGui.UI.Dispatcher.Invoke([action] {
$DateStamp = Get-Date -Format "yyyy-MM-dd-HH-mm-ss"
$TextRun = New-Object System.Windows.Documents.Run
$TextRun.Foreground = "Red"
$TextRun.Text = $DateStamp
$Paragraph = New-Object System.Windows.Documents.Paragraph($TextRun)
$TextRun = New-Object System.Windows.Documents.Run
$TextRun.Foreground = "#FF9A9A9A"
$TextRun.Text = ":"
$Paragraph.Inlines.Add($TextRun)
# $WPFGui."$Stream".AppendText($TextRun)
$TextRun = New-Object System.Windows.Documents.Run
$TextRun.Foreground = "#FF0078D7"
$TextRun.Text = $Prefix
$Paragraph.Inlines.Add($TextRun)
# $WPFGui."$Stream".AppendText($TextRun)
$TextRun = New-Object System.Windows.Documents.Run
$TextRun.Foreground = "#FF9A9A9A"
$TextRun.Text = ": "
$Paragraph.Inlines.Add($TextRun)
# $WPFGui."$Stream".AppendText($TextRun)
$TextRun = New-Object System.Windows.Documents.Run
if ( $IsError ) {
$TextRun.Foreground = "Red"
}
else {
$TextRun.Foreground = "Black"
}
$TextRun.Text = $Text
$Paragraph.Inlines.Add($TextRun) | Out-Null
# $WPFGui."$Stream".AppendText($TextRun)
$WPFGui."$Stream".Document.Blocks.Add($Paragraph) | Out-Null
$WPFGui."$Stream".ScrollToEnd()
})
}
$SessionFunctions.Add('Write-Activity') | Out-Null
function Write-StatusBar {
<#
.SYNOPSIS
Write text, and a progress percentage, to the StatusBar area
.DESCRIPTION
Writes Status text and progress to the StatusBar area.
.PARAMETER Progress
Progress Bar value, from 0-100
.PARAMETER Text
Status Text to display
.EXAMPLE
Write-StatusBar -Progress 25 -Text "We're a quarter of the way there."
.NOTES
If you change the name of $WPFGui, you'll need to change it here.
.INPUTS
Text
.OUTPUTS
None
#>
param (
# Prefix text, describe which part is printing output
[Parameter(Mandatory = $true)]
[int]
$Progress,
# Text to be printed
[Parameter(Mandatory = $true)]
[string]
$Text
)
$WPFGui.UI.Dispatcher.invoke([action] {
$WPFGui.Progress.Value = $Progress
$WPFGui.StatusText.Text = $Text
})
}
$SessionFunctions.Add('Write-StatusBar') | Out-Null
function Save-Screenshot {
<#
.SYNOPSIS
Save a Screenshot of the specified screen(s)
.DESCRIPTION
Save a screenshot of the specified screen(s) in the specified format. Valid formats are BMP, JPG/JPEG, and PNG.
.PARAMETER FilePath
Filename to save to
.PARAMETER Format
An optional parameter to specify a specific format. If not set, then the format is guessed from FilePath
.PARAMETER ScreenNumber
An int specifying which screen to save.
.PARAMETER AllScreens
A switch to specify a capture of all screens.
.EXAMPLE
Save-ScreenShot -FilePath "ScreenShot.jpg" -Format 'JPG' -ScreenNumber = 0
.NOTES
If you change the name of $WPFGui, you'll need to change it here.
.INPUTS
None
.OUTPUTS
Screenshot
#>
param (
[Parameter(Mandatory = $true)]
[string]$FilePath,
[Parameter(Mandatory = $false)]
[ValidateSet("BMP", "JPG", "JPEG", "PNG", "Unspecified")]
[string]$Format = "Unspecified",
[Parameter(Mandatory = $false)]
[ValidateScript({
((0 -le $_) -and ( $_ -le (([System.Windows.Forms.Screen]::AllScreens).Count - 1) ))
})]
[int16]$ScreenNumber,
[Parameter(Mandatory = $false)]
[Switch]$AllScreens
)
$ScreenList = [System.Windows.Forms.Screen]::AllScreens
$Top = 0
$Bottom = 0
$Left = 0
$Right = 0
if ($AllScreens) {
foreach ($CurrentScreen in $ScreenList) {
$Bounds = $CurrentScreen.Bounds
if ($Top -gt $Bounds.Top) {
$Top = $Bounds.Top
}
if ($Left -gt $Bounds.Left) {
$Left = $Bounds.Left
}
if ($Bottom -lt $Bounds.Bottom) {
$Bottom = $Bounds.Bottom
}
if ($Right -lt $Bounds.Right) {
$Right = $Bounds.Right
}
}
$Width = $Right - $Left
$Height = $Bottom - $Top
}
else {
$Left = $ScreenList[$ScreenNumber].Bounds.Left
$Top = $ScreenList[$ScreenNumber].Bounds.Top
$Right = $ScreenList[$ScreenNumber].Bounds.Right
$Bottom = $ScreenList[$ScreenNumber].Bounds.Bottom
$Width = $ScreenList[$ScreenNumber].Bounds.Width
$Height = $ScreenList[$ScreenNumber].Bounds.Height
}
$Bounds = [Drawing.Rectangle]::FromLTRB($Left, $Top, $Right, $Bottom)
$Bitmap = New-Object Drawing.Bitmap $Width, $Height
$Graphics = [Drawing.Graphics]::FromImage($Bitmap)
$Graphics.CopyFromScreen($Bounds.Location, [Drawing.Point]::Empty, $Bounds.Size)
try {
if ($Format -eq 'Unspecified') {
$Format = $FilePath.Split('.')[-1]
}
}
catch {
Write-Error "Unable to determine filetype from $FilePath"
}
$Bitmap.Save($FilePath, [System.Drawing.Imaging.ImageFormat]::$Format)
$Graphics.Dispose()
$Bitmap.Dispose()
}
$SessionFunctions.Add('Save-Screenshot') | Out-Null
Function Get-FileName() {
<#
.SYNOPSIS
Use a Win32 FileDialog to request a Filename from the user.
.DESCRIPTION
Shows a Win32 FileOpenDialog or FleSaveDialog to request a Filename from the User
.PARAMETER Title
The window Title
.PARAMETER Filter
The FileType filter, see Microsoft's documentation on this.
.PARAMETER InitialDirectory
The Initial Directory to display
.PARAMETER FileName
The default FileName to select
.PARAMETER Save
A switch to indicate that this is a SAVE dialog and not an OPEN dialog.
.EXAMPLE
$FileNameParameters = @{
Title = 'New Log File Name'
Filter = 'LOG Files (*.LOG)|*.log|HTML Files (*.html)|*.html|RTF Files (*.rtf)|*.rtf'
FileName = "$(Get-Date -Format 'yyyy-MM-dd-HHmmss').log"
Save = $true
}
$FileName = Get-FileName @FileNameParameters
.NOTES
.INPUTS
Text
.OUTPUTS
FileName
#>
Param(
[Parameter()][string]$Title = 'Open File',
[Parameter()][string]$Filter = 'All Files (*.*)|*.*',
[Parameter()][string]$InitialDirectory = "$($env:HOMEDRIVE)$($env:HOMEPATH)",
[Parameter()][string]$FileName = 'File.log',
[switch]$Save
)
$Result = $false
# Setup and open an "Open File" Dialog
If ( $Save ) {
$FileDialog = New-Object Microsoft.Win32.SaveFileDialog
$FileDialog.FileName = $FileName
}
else {
$FileDialog = New-Object Microsoft.Win32.OpenFileDialog
}
$FileDialog.Title = $Title
$FileDialog.filter = $Filter
$FileDialog.initialDirectory = $InitialDirectory
$FileDialog.AddExtension = $true
$FileDialogResult = $FileDialog.ShowDialog($owner)
#"OK clicked" status
$DialogOK = [System.Windows.Forms.DialogResult]::OK
if ($FileDialogResult -eq $DialogOK) {
$Result = $FileDialog
}
return $Result.FileName
}
$SessionFunctions.Add('Get-FileName') | Out-Null
Function Get-FolderName() {
<#
.SYNOPSIS
(Ab)Use a Win32 FileOpenDialog to request a FolderName from the user.
.DESCRIPTION
Shows a Win32 FileOpenDialog to request a FolderName from the User
.EXAMPLE
$FolderName = Get-FolderName
.NOTES
.INPUTS
None
.OUTPUTS
FolderName
#>
$Result = $false
# Setup and open an "Open File" Dialog
$FileDialog = New-Object Microsoft.Win32.OpenFileDialog
$FileDialog.Filter = "Select Folder|(Select Folder)"
$FileDialog.Title = "Select Folder"
$FileDialog.FileName = "Select Folder"
$FileDialog.CheckFileExists = $false
$FileDialog.ValidateNames = $false
$FileDialog.CheckPathExists = $true
$FileDialogResult = $FileDialog.ShowDialog()
if ($FileDialogResult) {
$Result = $FileDialog.FileName.Replace('Select Folder', '')
}
return $Result
}
$SessionFunctions.Add('Get-FolderName') | Out-Null
function ConvertFrom-FlowDocument {
<#
.SYNOPSIS
Converts a FlowDocument to HTML
.DESCRIPTION
Converts a (simple) RichTextBox FlowDocument to HTML
.PARAMETER Document
The FlowDocument to Convert
.PARAMETER Title
The HTML page Title to use
.PARAMETER Save
A switch to indicate that this is a SAVE dialog and not an OPEN dialog.
.EXAMPLE
$html = ConvertFrom-FlowDocument -Document $Document -Title $Title
.NOTES
.INPUTS
FlowDocument and Title
.OUTPUTS
HTML document
#>
param (
[Parameter(Mandatory = $True, HelpMessage = 'System.Windows.Documents.FlowDocument to convert to HTML', Position = 1)]
[System.Windows.Documents.FlowDocument]$Document,
[Parameter(Mandatory = $false, HelpMessage = 'Document Title', Position = 2)]
[string]$Title = ""
)
$html = @"
<html>
<head>
<title>
$Title
</title>
</head>
<body style=`"font-family: monospace;`">`n
$($Document.Blocks.ForEach({
$_.Inlines.Foreach({" <span style=`"color:$($_.Foreground.Color.ToString().Replace('#FF','#'))`">$($_.Text)</span>"})
"<br/>`n"
}))
</body>
</html>
"@
return $html
}
$SessionFunctions.Add('ConvertFrom-FlowDocument') | Out-Null
function Save-FlowDocument {
<#
.SYNOPSIS
Saves a RichTextBox FlowDocument in the requested format - LOG, RTF, or HTML.
.DESCRIPTION
Saves a RichTextBox FlowDocument as Plain Text (LOG), RichText (RTF), or HTML
.PARAMETER Document
FlowDocument to Save
.PARAMETER Format
The File Format to use - TXT, RTF, or HTML
.PARAMETER Title
The HTML page Title to use
.PARAMETER FileName
The name of the file to write.
.EXAMPLE
Save-FlowDocument -Document $WPFGui.Output.Document -Format $Format -FileName $FileName -Title "Example logs $(Get-Date -Format 'yyyy-MM-dd-HH-mm-ss')"
.NOTES
.INPUTS
FlowDocument and Title
.OUTPUTS
HTML document
#>
param (
[Parameter(Mandatory = $True, HelpMessage = 'Windows FlowDocument', Position = 1)]
[System.Windows.Documents.FlowDocument]$Document,
[Parameter(Mandatory = $True, HelpMessage = 'Format to save - txt, html, or rtf', Position = 2)]
[ValidateSet("TXT", "RTF", "HTML")]
[string]$Format,
[Parameter(Mandatory = $false, HelpMessage = 'HTML Document Title', Position = 3)]
[string]
$Title,
[Parameter(Mandatory = $True, HelpMessage = 'Filename', Position = 4)]
[string]
$FileName
)
# The TextRange is used for the TXT and RTF formats because it has a built-in .Save() method.
$TextRange = [System.Windows.Documents.TextRange]::new($Document.ContentStart, $Document.ContentEnd)
# This is used for all three
$FHand = [System.IO.FileStream]::new($FileName, 'OpenOrCreate')
switch ($Format) {
'txt' {
$TextRange.Save($FHand, [System.Windows.DataFormats]::Text)
}
'html' {
# Convert the FlowDocument to HTML, cast it to a collection of bytes and write it to FHand.
$html = ConvertFrom-FlowDocument -Document $Document -Title $Title
$htmlBytes = [byte[]][char[]]$html
$FHand.Write($htmlBytes, 0, $htmlBytes.Count)
}
'rtf' {
$TextRange.Save($FHand, [System.Windows.DataFormats]::Rtf)
}
}
$FHand.Flush()
$FHand.Close()
}
$SessionFunctions.Add('Save-FlowDocument') | Out-Null
# Create an Initial Session State for the ASync runspace and add all the functions in $SessionFunctions to it.
$InitialSessionState = [System.Management.Automation.Runspaces.InitialSessionState]::CreateDefault()
$SessionFunctions.ForEach({
$SessionFunctionEntry = [System.Management.Automation.Runspaces.SessionStateFunctionEntry]::new($_, (Get-Content Function:\$_))
$InitialSessionState.Commands.Add($SessionFunctionEntry) | Out-Null
})
#endregion Utility Functions
#region - Setup default values
# Development Mode Toggle
$DevMode = $true
# Failure Sentry. We watch this to know whether we need to bail out before trying to show the main window.
$Failed = $false
if ($DevMode) {
# If DevMode is set, we should read the UI definitions from these paths. This is useful when editing the XaML in Blend or Visual Studio.
$XaMLWindowPath = '<full path to>\MainWindow.xaml'
$XaMLResourceDictionaryPath = '<full path to>\ControlTemplates.XaML'
# Load the UI definition.
$WPFXaML = Get-Content -Raw -Path $XaMLWindowPath
$ResourceXaML = Get-Content -Raw -Path $XaMLResourceDictionaryPath
# If DevMode is set, override normal pwd/cwd detection and set it explicitly.
$ScriptPath = "<SomePath>"
}
else {
if ($MyInvocation.MyCommand.CommandType -eq "ExternalScript") {
$ScriptPath = Split-Path -Parent -Path $MyInvocation.MyCommand.Definition
}
else {
$ScriptPath = Split-Path -Parent -Path ([Environment]::GetCommandLineArgs()[0])
if (!$ScriptPath) { $ScriptPath = "." }
}
$mainWindow = $PSScriptRoot + ".\MainWindow.xaml"
$WPFXaML = Get-Content $mainWindow -Raw
$mainWindow = $PSScriptRoot + ".\ControlTemplates.xaml"
$ResourceXaML = Get-Content $mainWindow -Raw
}
#endregion
#region Build the GUI
try {
$WPFGui = [hashtable]::Synchronized( (New-WPFDialog -XamlData $WPFXamL -Resources $ResourceXaML) )
$WPFGui.Add('ResourceXaML', $ResourceXaML)
}
catch {
$failed = $true
}
$WPFGui.Add('hWnd', $null)
$WPFGui.Add('AsyncResult', [PSCustomObject]@{
Success = $false
Message = ""
})
#endregion
#region Titlebar buttons
$WPFGui.MinimizeButton.add_Click( {
$WPFGui.UI.WindowState = 'Minimized'
})
$WPFGui.RestoreButton.add_Click( {
$WPFGui.UI.WindowState = 'Normal'
$WPFGui.MaximizeButton.Visibility = 'Visible'
$WPFGui.RestoreButton.Visibility = 'Collapsed'
})
$WPFGui.MaximizeButton.add_Click( {
$WPFGui.UI.WindowState = 'Maximized'
$WPFGui.MaximizeButton.Visibility = 'Collapsed'
$WPFGui.RestoreButton.Visibility = 'Visible'
})
$WPFGui.CloseButton.add_Click( {
$WPFGui.UI.Close()
})
#endregion Titlebar buttons
#region Menu Buttons
$WPFGui.SaveLogs.add_Click( {
# When clicked, save the Activity Log to a file.
$FileNameParameters = @{
Title = 'New Log File Name'
Filter = 'LOG Files (*.LOG)|*.log|HTML Files (*.html)|*.html|RTF Files (*.rtf)|*.rtf'
FileName = "$(Get-Date -Format 'yyyy-MM-dd-HHmmss').log"
Save = $true
}
$FileName = Get-FileName @FileNameParameters
if ( $FileName ) {
$Extension = ([System.IO.FileInfo]$FileName).Extension
$Format = $Extension.Replace('.', '').Replace('log', 'txt')
Save-FlowDocument -Document $WPFGui.Output.Document -Format $Format -FileName $FileName -Title "PoSH GUI Template logs $(Get-Date -Format 'yyyy-MM-dd-HH-mm-ss')"
}
})
$WPFGui.MenuAllgemein.add_Click({
$WPFGUI.TabControl.SelectedIndex = 0
$WPFGui.MenuButton.RaiseEvent((New-Object -TypeName System.Windows.RoutedEventArgs -ArgumentList $([System.Windows.Controls.Button]::ClickEvent)))
})
$WPFGui.MenuBenutzer.add_Click({
$WPFGUI.TabControl.SelectedIndex = 1
$WPFGui.MenuButton.RaiseEvent((New-Object -TypeName System.Windows.RoutedEventArgs -ArgumentList $([System.Windows.Controls.Button]::ClickEvent)))
})
$WPFGui.MenuComputer.add_Click({
$WPFGUI.TabControl.SelectedIndex = 2
$WPFGui.MenuButton.RaiseEvent((New-Object -TypeName System.Windows.RoutedEventArgs -ArgumentList $([System.Windows.Controls.Button]::ClickEvent)))
})
$WPFGui.MenuExit.add_Click({
$WPFGui.UI.Close()
})
#endregion
#region Main Program buttons and fields
$WPFGui.MenuOpen.add_Completed( {
# Flip the end points of the menu animation so that it will open when clicked and close when clicked again
$AnimationParts = @('MenuToggle', 'BurgerFlipper', 'BlurPanel')
foreach ($Part in $AnimationParts) {
$To = $WPFGui."$Part".To
$From = $WPFGui."$Part".From
$WPFGui."$Part".To = $From
$WPFGui."$Part".From = $To
}
})
$WPFGui.UI.add_ContentRendered( {
# Once the window is visible, grab handle to it
if ( $WPFGui.hWnd -eq $null) {
$WPFGui.hWnd = (New-Object System.Windows.Interop.WindowInteropHelper($WPFGui.UI)).Handle
}
[System.Win32Util]::SetTop($WPFGui.hWnd)
# Write an example log entry
Write-Activity -Prefix 'PoSH GUI Template' -Text 'Example Activity Log Entry' -Stream 'Output'
})
#endregion
# This region is unique to the example UI and is meant to demonstrate how to code-behind the various controls
#region Setup items for static comboxes
# The best way to bind data to list controls - ComboBoxes, ListBoxes, DataGrids, et. al. - is to use an ObservableCollection
# Create an ObservableCollection for the example DataGrid
$WPFGui.Add('ExampleGridItemsList', (New-Object System.Collections.ObjectModel.ObservableCollection[PSCustomObject]) )
# Set the ObservableCollection as the ItemsSource for the DataGrid
$WPFGui.ExampleGrid.ItemsSource = $WPFGUI.ExampleGridItemsList
# Some sample data for the grid, in CSV format
$ExampleGridItems = @'
"CheckBox","Description","Filename","ExtraInfo","RowIsValid"
"True","Lorem ipsum dolor sit","amet.xslx","consectetur adipiscing elit","True"
"False","sed do eiusmod tempor","incididunt.txt","ut labore et dolore magna aliqua.","True"
"True","Ut enim ad minim veniam","quis.doc","nostrud exercitation ullamco","False"
'@ | ConvertFrom-Csv
# Add each row to the the ObservableCollection. The DataGrid will displat this data automatically
$ExampleGridItems.Foreach({ $WPFGui.ExampleGridItemsList.Add($_) | Out-Null })
# The ComboBoxes work similarly to the DataGrid.
$WPFGui.Add('ComboBox1List', (New-Object System.Collections.ObjectModel.ObservableCollection[string]) )
$WPFGui.ComboBox1.ItemsSource = $WPFGui.ComboBox1List
foreach ($BoxItem in ('laboris nisi ut aliquip').Split(' ')) {
$WPFGUI.ComboBox1List.Add([string]$BoxItem) | Out-Null
}
$WPFGui.ComboBox1.Items.Refresh()
$WPFGui.Add('ComboBox2List', (New-Object System.Collections.ObjectModel.ObservableCollection[string]) )
$WPFGui.ComboBox2.ItemsSource = $WPFGui.ComboBox2List
foreach ($BoxItem in ('ex ea commodo consequat').Split(' ')) {
$WPFGUI.ComboBox2List.Add([string]$BoxItem) | Out-Null
}
$WPFGui.ComboBox2.Items.Refresh()
# Defaulted values for other input types
$WPFGUI.Add('TextBox1Text', "dolor")
$WPFGUI.TextBox1.Text = $WPFGUI.TextBox1Text
$WPFGui.Add('pwd', $ScriptPath)
$WPFGui.Add('DomainList', (New-Object System.Collections.ObjectModel.ObservableCollection[string]))
foreach ($Domain in @('Duis aute irure').Split(' ')) {
$WPFGUI.DomainList.Add($Domain) | Out-Null
}
$WPFGUI.UserDomain.ItemsSource = $WPFGUI.DomainList
# Not used here, but included for completeness. This is a ButtonClick event that can be routed to a given button on the window.
$WPFGui.Add('ButtonClick', (New-Object -TypeName System.Windows.RoutedEventArgs -ArgumentList $([System.Windows.Controls.Button]::ClickEvent)))
$WPFGUI.SetPath.Add_Click({
# Show a Folder Selection dialog when clicked.
$Parameters = @{
Title = 'Select the folder containing explorer.exe'
}
$ExplorerPath = Get-FolderName @Parameters
$ExplorerFile = 'explorer.exe'
if ( -not (Test-Path (Join-Path $ExplorerPath $ExplorerFile))) {
$WPFGui.TextBox2.Foreground = "#FFFF0000"
}
else {
$WPFGui.TextBox2.Foreground = "#FF000000"
}
$WPFGui.TextBox2.Text = $(Join-Path $ExplorerPath $ExplorerFile)
})
$WPFGui.Execute.add_Click({
# Run this code when execute is clicked. A sample dialog is shown synchronously, then the variable values are printed asynchronously.
Set-Blur -On
$NewDialog = @{
DialogTitle = 'Example Dialog'
H1 = "This is a pop-up dialog"
DialogText = "Dialog text should go here"
ConfirmText = 'Continue'
GetInput = $false
Beep = $true
IsError = $false
Owner = $WPFGui.UI
}
$Dialog = New-MessageDialog @NewDialog
Set-Blur -Off
$AsyncParameters = @{
Variables = @{
WPFGui = $WPFGui
ComboBox1Value = $WPFGui.ComboBox1.SelectedValue
ComboBox2Value = $WPFGui.ComboBox2.SelectedValue
CheckBox1Check = $WPFGui.CheckBox1.IsChecked
TextBox1Text = $WPFGui.TextBox1.Text
TextBox2Text = $WPFGui.TextBox2.Text
Domain = $WPFGui.UserDomain.SelectedValue
Username = $WPFGui.UserName.Text
SecurePassword = $WPFGui.Password.SecurePassword
GridDataJSON = $WPFGui.ExampleGrid.Items | ConvertTo-Json
}
Code = {
Write-Activity -Stream 'Output' -Prefix 'Example' -Text "ComboBox1 Value: $ComboBox1Value"
Write-StatusBar -Progress 12.5 -Text "ComboBox1 Value"
# Start-Sleep -Seconds 1
Write-Activity -Stream 'Output' -Prefix 'Example' -Text "ComboBox2 Value: $ComboBox2Value"
Write-StatusBar -Progress 25 -Text "ComboBox2 Value"
# Start-Sleep -Seconds 1
Write-Activity -Stream 'Output' -Prefix 'Example' -Text "CheckBox1 is $(if (-not $CheckBox1Check) { "not " })checked."
Write-StatusBar -Progress 37.5 -Text "TextBox1 Value"
# Start-Sleep -Seconds 1
Write-Activity -Stream 'Output' -Prefix 'Example' -Text "TextBox1 Text: $TextBox1Text"
Write-StatusBar -Progress 50 -Text "TextBox1 Value"
# Start-Sleep -Seconds 1
Write-Activity -Stream 'Output' -Prefix 'Example' -Text "TextBox2 Text: $TextBox2Text"
Write-StatusBar -Progress 62.5 -Text "TextBox2 Value"
# Start-Sleep -Seconds 1
Write-Activity -Stream 'Output' -Prefix 'Example' -Text "Credential Values: $Domain\$Username $SecurePassword"
Write-StatusBar -Progress 87.5 -Text "Credential Values: $Domain\$Username $SecurePassword"
# Start-Sleep -Seconds 1
Write-Activity -Stream 'Output' -Prefix 'Example' -Text "DataGrid Values (as JSON) $GridDataJSON"
Write-StatusBar -Progress 100 -Text "DataGrid Values (as JSON)"
# Start-Sleep -Seconds 1
Write-StatusBar -Progress 0 -Text "Ready."
}
}
Invoke-Async @AsyncParameters
})
#endregion
if ( -not $Failed ) {
# Setup async runspace items
$WPFGui.Host = $host
$WPFGui.Add('Runspace', [runspacefactory]::CreateRunspace($InitialSessionState))
$WPFGui.Runspace.ApartmentState = "STA"
$WPFGui.Runspace.ThreadOptions = "ReuseThread"
$WPFGui.Runspace.Open()
$WPFGui.UI.Dispatcher.InvokeAsync{ $WPFGui.UI.ShowDialog() }.Wait()
$WPFGui.Runspace.Close()
$WPFGui.Runspace.Dispose()
}
})
$psCmd.Runspace = $newRunspace
$psCmd.Invoke()