PowerShell WPF Printing - Can Not Get the IDocumentPaginatorSource Interface - wpf

I am trying to use WPF printing from PowerShell. I have found a simple example in VB.Net here:
'Create a new Run class, passing it the text.
' The Run class contains 1 line of text
Dim r As New Run(text)
'Create a new paragraph, passing it the new run class instance
Dim ph As New Paragraph(r)
'Create the document, passing a new paragraph
Dim doc As New FlowDocument(ph)
doc.PagePadding = New Thickness(100) 'Creates margin around the page
'Send the document to the printer
diag.PrintDocument( _
CType(doc, IDocumentPaginatorSource).DocumentPaginator, _
printCaption)
but I am unable to convert it to PowerShell. Here is my attempt:
$run = New-Object System.Windows.Documents.Run('text')
$paragraph = New-Object System.Windows.Documents.Paragraph($run)
$flowDocument = New-Object System.Windows.Documents.FlowDocument($paragraph)
$thickness = New-Object System.Windows.Thickness(100)
$flowDocument.PagePadding = $thickness
$flowDocument -is [System.Windows.Documents.IDocumentPaginatorSource]
$source = $flowDocument -as [System.Windows.Documents.IDocumentPaginatorSource]
$source.DocumentPaginator -eq $null
$printDialog = New-Object System.Windows.Controls.PrintDialog
$printDialog.PrintDocument($source.DocumentPaginator, "test")
$source.DocumentPaginator seems to be null, and an exception is raised. (The VB.Net code works)
[Edit]
Here is another attempt that failed:
Add-Type -Assembly 'ReachFramework'
$flowDocument = New-Object System.Windows.Documents.FlowDocument
$textRange = New-Object System.Windows.Documents.TextRange(
$flowDocument.ContentStart, $flowDocument.ContentEnd)
$textRange.Text = 'Text'
$xpsDocument = New-Object System.Windows.Xps.Packaging.XpsDocument(
"C:\scripts\test.xps", [System.IO.FileAccess]::ReadWrite)
$xpsDocumentWriter =
[System.Windows.Xps.Packaging.XpsDocument]::CreateXpsDocumentWriter(
$xpsDocument)
$source = $flowDocument -as [System.Windows.Documents.IDocumentPaginatorSource]
$xpsDocumentWriter.Write($source.DocumentPaginator)
$xpsDocument.Close()
I was trying to use one of the XpsDocumentWriter.Write() overloads to send the FlowDocument to a printer, but it failed, $source.DocumentPaginator is null. I didn't even manage to create a .xps file and save it.

Sometimes when I get frustrated with PowerShell's language "issues", I drop back to C# which V2 makes very easy. The following prints for me:
$src = #'
public static System.Windows.Documents.DocumentPaginator
GetDocumentPaginator(System.Windows.Documents.FlowDocument flowDocument)
{
return ((System.Windows.Documents.IDocumentPaginatorSource)flowDocument).DocumentPaginator;
}
'#
Add-Type -MemberDefinition $src -Name Utils -Namespace Foo `
-ReferencedAssemblies WindowsBase,PresentationFramework,
PresentationCore
$run = New-Object System.Windows.Documents.Run('text')
$paragraph = New-Object System.Windows.Documents.Paragraph($run)
$flowDocument = New-Object System.Windows.Documents.FlowDocument($paragraph)
$thickness = New-Object System.Windows.Thickness(100)
$flowDocument.PagePadding = $thickness
$paginator = [Foo.Utils]::GetDocumentPaginator($flowDocument)
$printDialog = New-Object System.Windows.Controls.PrintDialog
$printDialog.PrintDocument($paginator, "test")
Note that you could do the same with VB code. Just use the -Language VisualBasic parameter on Add-Type.

Related

PowerShell Version 4.0 (.PS1 File) - Add Text from TextBox into an Array to be printed on the console

Please Note this is PowerShell 4.0. I don't think Convert-String is available to , but granted, answers for other versions of PowerShell are still worth the read.
Alright. I'm very new to PowerShell, but interestingly enough both through Google and StackOverFlow I haven't been able to find the answer to this; I'm just not sure where to go yet.
Please keep in mind that i want to launch the file example.ps1 from the PowerShell prompt.
Function Show_Form
{
Add-Type -AssemblyName System.Windows.Forms
$global:Y_Position = 10
$global:X_Position = 5
$Form = New-Object system.Windows.Forms.Form
$Form.Text = "Sample Form"
$Form.Width = 400
$Form.Height = 450
$Form.AutoScroll = $True
$Form.AutoSize = $True
$Form.AutoSizeMode = "GrowOnly"
$Form.MinimizeBox = $False
$Form.MaximizeBox = $False
$Label = New-Object System.Windows.Forms.Label
$Label.Text = "Please Fill in the Required Information:`n"
$Label.AutoSize = $True
$Form.Controls.Add($Label)
$LabelTextBox = New-Object System.Windows.Forms.Label
$LabelTextBox.Location = New-Object System.Drawing.Point(10,40)
$LabelTextBox.Text = $Text
$LabelTextBox.AutoSize = $True
$Form.Controls.Add($LabelTextBox)
$global:textBox = New-Object System.Windows.Forms.TextBox
$X_Position = $X_Position+90
$textBox.Location = New-Object System.Drawing.Point(10,40)
$textBox.Size = New-Object System.Drawing.Size(260,20)
$Form.Controls.Add($textBox)
$Okbutton = New-Object System.Windows.Forms.Button
$Okbutton.Location = New-Object System.Drawing.Size(250,570)
$Okbutton.Size = New-Object System.Drawing.Size(100,30)
$Okbutton.Text = "OK"
<#-----------------------------------#>
$global:Arr = #("lll", "ppp", 2)
$OkButton.Add_Click({[System.Windows.Forms.MessageBox]::Show($textBox.text, "My Dialog Box")})
$OkButton.Add_Click({Write-Host $textBox.text})
$global:Arr[0] = $textBox.text
$OkButton.Add_Click({Write-Host $Arr})
<#-----------------------------------#>
$Okbutton.Add_Click({$Form.Close()})
$Form.Controls.Add($Okbutton)
$Form.ShowDialog()
}
Show_Form
My problem is with these lines:
$global:Arr = #("lll", "ppp", 2)
$OkButton.Add_Click({[System.Windows.Forms.MessageBox]::Show($textBox.text, "My Dialog Box")})
$OkButton.Add_Click({Write-Host $textBox.text})
$global:Arr[0] = $textBox.text
$OkButton.Add_Click({Write-Host $Arr})
The Add_Click({Write-Host $textBox.text}) displays in the Console but when I add $textBox.text to an array (object array or otherwise) or replace another element with it then print the Array, it is always just blank.
.
And if I do the following (Note: Arr[0] AND Arr[1])
$global:Arr = #("lll", "ppp", 2)
$OkButton.Add_Click({[System.Windows.Forms.MessageBox]::Show($textBox.text, "My Dialog Box")})
$OkButton.Add_Click({Write-Host $textBox.text})
$global:Arr[0] = $textBox.text
$OkButton.Add_Click({Write-Host $Arr})
$global:Arr[1] = "$textBox.text"
$OkButton.Add_Click({Write-Host $Arr})
Both elements get written as the value System.Windows.Forms.TextBox, Text: .text
I'm wondering how to make $textBox.text display in the Console in the array?
The problem you face is simple.
You assumed that $global:Arr[1] = "$textBox.text" would be set the actual text value from when you click the Ok button in your form.
But actually, this section of code is called before $Form.ShowDialog(). Therefore, at that point in time, your textbox is indeed empty, which explains what you get.
If you want your global array to be set to the textbox text after the Ok button is pressed, you need to set the array value when the user click the button through the Add_Click event.
Like this:
$OkButton.Add_Click( { $global:Arr[0] = $textBox.text })
Additional note
If you're not familiar already with how debugging work in Powershell, I'd recommend you to look at some debugging tutorials.
You would have immediately seen that the script did go to your variable assignment before the form is even shown, which would have possibly clued you in on the situation.
--
You can see below a debug point on the assignment line. The execution is stopped before I had a chance to see or enter anything in the form, which indicates that the value clearly can't be the one from the form. Querying $textBox.text at that point from the console shows the value to be empty, as we would expect from a newly initialized form.
Some documentation: how to debug scripts (even though it says ISE, all of this should be good with VSCode too)

PowerShell: Duplicate windows forms with different properties

I am trying make copies of windows forms objects and change the properties of new objects. For example:
$List1 = New-Object System.Windows.Forms.ListBox
$List1.Location = New-Object System.Drawing.Size(10,10)
$List1.Size = New-Object System.Drawing.Size(280,310)
$List2 = $List1
$List2.Location = New-Object System.Drawing.Size(350,10)
The problem is that $List2 is a pointer of $List1. Whatever I change on $List2 always change the properties on $List1. Is there a solution for this?
$List1.Location
IsEmpty X Y
------- - -
False 350 10
$List1.Location
IsEmpty X Y
------- - -
False 350 10
Whatever I change on $List2 always change the properties on $List1. Is there a solution for this?
Yes, the solution is to create a new instance of ListBox:
$List1 = New-Object System.Windows.Forms.ListBox
$List1.Location = New-Object System.Drawing.Size(10,10)
$List1.Size = New-Object System.Drawing.Size(280,310)
$List2 = New-Object System.Windows.Forms.ListBox
$List2.Size = $List1.Size
$List2.Location = New-Object System.Drawing.Size(350,10)
Notice that $List2.Size = $List1.Size is safe, because Size is a struct, and structs are copied on assignment
If you have many properties to reference, you could wrap the common property values in a hashtable to pass to New-Object -Property:
$ListBoxDefaultProperties = #{
Location = New-Object System.Drawing.Size (10,10)
Size = New-Object System.Drawing.Size (280,310)
BackColor = 'Beige'
DisplayMember = 'SomePropertyName'
# etc...
}
$List1 = New-Object System.Windows.Forms.ListBox -Property $ListBoxDefaultProperties
$List2 = New-Object System.Windows.Forms.ListBox -Property $ListBoxDefaultProperties
$List3 = New-Object System.Windows.Forms.ListBox -Property $ListBoxDefaultProperties
$List1 and $List2 reference the same object because you did $List1 = $List2.
You have to create 2 separate instances. You can set all common properties in a loop and then only change the properties that differ:
# 1. create two separate instances
$list1 = New-Object System.Windows.Forms.ListBox
$list2 = New-Object System.Windows.Forms.ListBox
# 2. set properties on both instances
foreach ($list in ($list1, $list2)) {
$list.Location = New-Object System.Drawing.Size(10, 10)
$list.Size = New-Object System.Drawing.Size(280, 310)
# [...]
}
# 3. set all different properties on the 2nd instance only
$list2.Location = New-Object System.Drawing.Size(350, 10)
# [...]

How to limit number of lines in a TextBox and split the lines to strings?

How can I achieve that the input of the TextBox with multilines is only possible to a specific number of lines e.g. 10 lines only.
Further I want to get the input of each line and write each line to a separate variable to work later with this variables. It would be nice if the user gets a messagebox with warning that only 10 lines are possible.
Any help would be appreciated
[reflection.assembly]::loadwithpartialname("System.Windows.Forms") | Out-Null
[reflection.assembly]::loadwithpartialname("System.Drawing") | Out-Null
#Assembly PresentationFramework wird geladen
Add-Type -AssemblyName PresentationFramework
$form = New-Object System.Windows.Forms.Form
$form.StartPosition = 'CenterScreen' #Formstartposition Zentrum
$form.Size = New-Object System.Drawing.Size(500,400)
$textBox = New-Object System.Windows.Forms.TextBox
$textBox.DataBindings.DefaultDataSourceUpdateMode = 0
$textBox.Location = New-Object System.Drawing.Point((110),(90))
$textBox.Size = New-Object System.Drawing.Size(288,150)
$textBox.TabIndex = 0
$textBox.Multiline =$true
$form.Controls.Add($textBox)
$form.ShowDialog() | Out-Null
1.Get Line count on the event of TextChanged.
2.Turn the iList into a ArrayList.
3.Get the difference from max line to current line count.
4.Remove the range from the ArrayList.
5.Set the content of the Textbox to the Arraylist
6.Set the curser to end of textbox.
$TextboxMaxLines = 10
$textBox.Add_TextChanged({
If($textBox.Lines.Count -gt $TextboxMaxLines){
[System.Collections.ArrayList]$AL = $textBox.Lines
[int]$LC = ($textBox.Lines.Count - $TextboxMaxLines)
$Al.RemoveRange($TextboxMaxLines, $LC)
$textbox.Lines = $AL
$textbox.SelectionStart = ($textbox.Text.Length)
$textbox.SelectionLength = 0
}
})

PowerShell Forms - Vertical Progress Bar

This is regarding Windows Forms in PowerShell and the System.Windows.Forms.ProgressBar.
I am looking all over and cannot find anything that allows the progress bar to fill vertically. I've considered alternatives (such as resizing a label with a background color), but I prefer to use something that already has a class if possible. I could have sworn I had seen something out there like this before, even if it wasn't a true progress bar. I am not using it to track progress, but more for CPU usage, RAM usage, and drive space for server statuses. This is for a useful GUI for a quick report of servers from a dropdown list, something where I don't have to open another complete shell session (I have enough shells open as it is between O365 eDiscovery, data analysis, and other needs). Thanks for any suggestions in advance.
Here is a very good C# answer How do I make a winforms progress bar move vertically in C#?
It overrides the CreateParams method to set the PBS_VERTICAL flag in Style.
To make it work in PowerShell you will unfortunately have to use a bit of C# code.
This works for me:
$type = #'
using System;
using System.Windows.Forms;
public class VerticalProgressBar : ProgressBar {
protected override CreateParams CreateParams {
get {
CreateParams cp = base.CreateParams;
cp.Style |= 0x04;
return cp;
}
}
}
'#
Add-Type -TypeDefinition $type -ReferencedAssemblies System.Drawing,System.Data,System.Windows.Forms
$userForm = New-Object System.Windows.Forms.Form
$userForm.Text = "$title"
$userForm.Size = New-Object System.Drawing.Size(230,300)
$userForm.StartPosition = "CenterScreen"
$userForm.AutoSize = $False
$userForm.MinimizeBox = $False
$userForm.MaximizeBox = $False
$userForm.SizeGripStyle= "Hide"
$userForm.WindowState = "Normal"
$userForm.FormBorderStyle="Fixed3D"
$progressbar = New-Object 'VerticalProgressBar'
$progressbar.Location = New-Object System.Drawing.Point(180, 50);
$progressbar.Width = 20
$progressbar.Height = 200
$userForm.Controls.Add($progressbar)
$TrackBar = New-Object 'System.Windows.Forms.TrackBar'
$TrackBar.Location = New-Object System.Drawing.Point(10, 10);
$TrackBar.Width = 200
$TrackBar.add_ValueChanged({$progressbar.Value = $this.value*10})
$userForm.Controls.Add($TrackBar)
$OKButton = New-Object System.Windows.Forms.Button
$OKButton.Location = New-Object System.Drawing.Size(10,220)
$OKButton.Size = New-Object System.Drawing.Size(75,23)
$OKButton.Text = "OK"
$OKButton.Add_Click({$userForm.Close()})
$userForm.Controls.Add($OKButton)
$userForm.ShowIcon = $False
$userForm.Add_Shown({$userForm.Activate()})
$userForm.AcceptButton = $OKButton
[void] $userForm.ShowDialog()

Powershell Timer without pausing form

Good day all
As usual I am stuck, I have a simple script that is designed to show you the status of a list of systems bitlocker. Give it a txt of system names, it does the rest. All works as intended; however its updating the list on a ticking Timer, which when executing will make the window unresponsive and appear to be broken (to those users who dont understand what its doing). Is there a way to branch this off in some fashion to avoid this hangingup?
I considered doing a branch but I do now know how to make that branch update an object in its parent... if thats even possible.
CODE:
[void] [Reflection.Assembly]::LoadWithPartialName( 'System.Windows.Forms' )
$d = New-Object Windows.Forms.OpenFileDialog
$d.ShowHelp = $true
$d.filter = "System ID List (*.txt)| *.txt"
$result = $d.ShowDialog( )
$names = #()
$names = Get-Content $d.filename
[System.Reflection.Assembly]::LoadWithPartialName("System.Drawing")
[System.Reflection.Assembly]::LoadWithPartialName("System.windows.forms")
$myWindow = new-object System.Windows.Forms.form
$myDataGrid = new-object System.windows.forms.DataGridView
$myDataGrid.Location = new-object System.Drawing.Size(20,30)
$myDataGrid.size = new-object System.Drawing.Size(450,480)
$myDataGrid.AllowUserToAddRows = $False
$myDataGrid.AutoSizeColumnsMode = [System.Windows.Forms.DataGridViewAutoSizeColumnsMode]::Fill
$myDataGrid.RowsDefaultCellStyle.BackColor = [System.Drawing.Color]::Bisque
$myDataGrid.AlternatingRowsDefaultCellStyle.BackColor = [System.Drawing.Color]::Beige
$myDataGrid.BorderStyle = [System.Windows.Forms.BorderStyle]::Fixed3D
$myDataGrid.ColumnHeadersDefaultCellSTyle.ForeColor = [System.Drawing.Color]::Maroon
$myDataGrid.ColumnHeadersDefaultCellStyle.BackColor = [System.Drawing.Color]::Tan
$myDataGrid.RowHeadersDefaultCellStyle.BackColor = [System.Drawing.Color]::Tan
$myDataGrid.ColumnHeadersHeightSizeMode = [System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode]::AutoSize
$myWindow.Controls.Add($myDataGrid)
# Define menus
$myMenuStrip = new-object System.Windows.Forms.MenuStrip
$FileExit = new-object System.Windows.Forms.ToolStripMenuItem("&Exit")
$FileExit.add_Click({ $myWindow.close() })
$myMenuStrip.Items.Add($FileMenu)
$myWindow.Controls.Add($myMenuStrip)
$timer = New-Object System.Windows.Forms.Timer
$timer.Interval = 1000
$timer.add_tick({
$dataTable = New-Object System.Data.DataTable
$dataTable.Columns.Add("System") | Out-Null
$dataTable.Columns.Add("BitLocker % (C:)") | Out-Null
foreach ($name in $names) {
$stat = (manage-bde.exe -cn $name -status C:)[11].split(":")[1]
$row = $dataTable.NewRow()
$row["System"] = $name
$row["BitLocker % (C:)"] = $stat
$dataTable.Rows.Add($row)
}
$myDataGrid.DataSource = $dataTable
})
# main program body
$myWindow.Text = "BitLocker Status"
$myWindow.size = new-object System.Drawing.Size(500,600)
$myWindow.autoscroll = $true
$myWindow.Add_Shown({$myWindow.Activate()})
$timer.Start()
$myWindow.ShowDialog()
I cannot believe nobody answered this, perhaps I wasn't clear.
Eitherway the solution was easy, write-output $object, then receive-job.
Done

Resources