Goal:
I am attempting to create a button that would save a file to desktop. The incoming file is fetched with an Invoke-WebRequest using the GET method. I want the save button to be in my pop-up window.
Here is an example:
Side note:
This code is sitting in a switch with a variable split three ways.
switch (...) {
p {
if ($second -match 'RegexMatch') {
$resource = $second
$fileResult = Invoke-WebRequest -Uri https://url.com/$resource/file -WebSession $currentsession
# End API Call
Write-Host
Write-Host '------------' -ForegroundColor Green
Write-Host 'FILE Results' -ForegroundColor Green
Write-Host '------------' -ForegroundColor Green
# Create Window
Add-Type -AssemblyName System.Windows.Forms
$form = New-Object -TypeName System.Windows.Forms.Form
$form.StartPosition = 'CenterScreen'
$form.KeyPreview = $true
$form.Add_KeyDown {
if ($_.Control -and $_.KeyCode -eq 'F') {
Add-Type -AssemblyName Microsoft.VisualBasic
$stringToFind = [Microsoft.VisualBasic.Interaction]::InputBox('Please enter your search terms', 'Find')
$pos = $textBox.Text.IndexOf($stringToFind)
if ($pos -ne -1) {
$textBox.SelectionStart = $pos
$textBox.SelectionLength = $stringToFind.Length
}
}
}
# Textbox
$textBox = New-Object -TypeName System.Windows.Forms.TextBox
$textBox.Dock = [Windows.Forms.DockStyle]::Fill
$textBox.ReadOnly =$true
$textBox.Multiline = $true
$textBox.ScrollBars = 'Vertical'
$textBox.Font = New-Object -TypeName System.Drawing.Font -ArgumentList ('Arial',12)
$textBox.ForeColor = 'White'
$textBox.Text = $fileResult
$textBox.BackColor = 'Black'
$textBox.ShortcutsEnabled = $true
$Form.Controls.Add($textBox)
# Button
$btn = New-Object -TypeName System.Windows.Forms.Button
$btn.Text = 'Finish'
$btn.DialogResult = 'Ok'
$btn.Dock = 'bottom'
$form.Controls.Add($btn)
if ($form.ShowDialog() -eq 'Ok') {
$tb.lines
}
} else {
Write-Host
Write-Warning -Message 'Please enter a valid FILE ID'
Write-Host
}
break
}
...
}
Purpose:
I want to add an option for the user to download the file for a closer look in a different application.
Question:
How would I begin to create a button utilizing winforms in powershell to save this file to disk?
Here is what I have tried:
$BtnSave=New-Object -TypeName System.Windows.Forms.Button
$BtnSave.Text='Save'
$BtnSave.Dock='bottom'
$btnSave.DialogResult='Ok'
$form.Controls.Add($BtnSave)
$BtnSave.Add_Click({
$SaveFileDialog = New-Object 'System.Windows.Forms.SaveFileDialog'
if ($SaveFileDialog.ShowDialog() -eq 'Ok')
{
$textBox.Text = $SaveFileDialog.FileName
Write-Information 'File Saved'
}
})
New Problem:
File is not saving to disk still, but the save file dialog does show up on click. In addition, using Switch -OutFile with my Invoke-WebRequest is shooting me an error.
Error:
Invoke-WebRequest : Missing an argument for parameter 'OutFile'. Specify a parameter of type 'System.String' and try again.
After adding a button where you want in the form, the Add_Click() method will allow you to handle its click event and run any scriptblock you want (when button is clicked).
At this point, the -OutFile argument for Invoke-WebRequest will help with saving the downloaded file to disk (pass it the desired path to the file).
Related
TLDR:
How can I make a generated variable, and then call that variable later within a Add_click.
I am sure some kind of serialization of each Object/button I make is what is needed.
I am building a small tool that reads from a csv to create a button, and function.
the csv looks something like
Name Type Link Script
Powershell App C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe Empty
FixXYZ Fix Empty -ScriptStuffHere-
The tool will then make a button with the Name, (work in progress to filter apps and fixes), and when you click the button, if its an app will do start ($link) and if its a fix it will run that script.
My issue is I have it making the button and giving them names, and the name of the button stays, but the function does not.
full code:
Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName PresentationFramework
[System.Windows.Forms.Application]::EnableVisualStyles()
#=======================================================
$Form = New-Object system.Windows.Forms.Form
$Form.text = "Form"
$Form.TopMost = $false
$Form.ClientSize = New-Object System.Drawing.Point(760,400)
$Form.minimumSize = New-Object System.Drawing.Size(760,400)
$Form.maximumSize = New-Object System.Drawing.Size(760,400)
$GetCSV = import-csv "C:\File.csv"
$Details = $GetCSV.Name
$DeviceList = $GetCSV
$Count = $DeviceList.Lines.Count
$ObjectNumber = -1
Write-Host "Total Entries:" $Count
$x = 0 #up down
$z = 0 #left right
$Names = #($DeviceList.Lines)
$Names | ForEach-Object{
$ObjectNumber += 1
Write-Host "Object:" $ObjectNumber
$x += 0
$z += 120
if($z -eq 720){
$x += 120
$z = 0
Write-Host "New Row"}
Write-Host "x" $x
Write-Host "z" $z
$ButtonLabel = ($GetCSV[$ObjectNumber]).Name
set-Variable -Name "var$ObjectNumber" -Value ($GetCSV[$ObjectNumber] | Select Name, Type, Link, Script, File, FileSource)
Write-Host "Name: " (Get-Variable -Name "var$ObjectNumber" -ValueOnly).Name
Write-Host "Type: " (Get-Variable -Name "var$ObjectNumber" -ValueOnly).Type
Write-Host "Link: "(Get-Variable -Name "var$ObjectNumber" -ValueOnly).Link
Write-Host "Script: "(Get-Variable -Name "var$ObjectNumber" -ValueOnly).Script
Write-Host "File: "(Get-Variable -Name "var$ObjectNumber" -ValueOnly).File
Write-Host =========================
$_ = New-Object system.Windows.Forms.Button
$_.text = $ButtonLabel
$_.width = 100
$_.height = 100
$_.location = New-Object System.Drawing.Point($z,$x)
$_.Font = New-Object System.Drawing.Font('Microsoft Sans Serif',10)
$_.Add_Click({ Start (Get-Variable -Name "var$ObjectNumber" -ValueOnly).Link})
$Form.Controls.Add($_)
}
[void]$Form.ShowDialog()
I am very certain my issue is coming from
$_.Add_Click({Start (Get-Variable -Name "var$ObjectNumber" -ValueOnly).Link})
I know the issue is with $ObjectNumber because that number is getting +1 each time the ForEach is gone through, so when I click a button, its taking "var$OjbectNumber" as its Last number. Clicking the button works, but all buttons open the last entries link.
The answer was using a unused property to throw my desired call back variable in.
So in this case, i have a folder with with programs, the button will be made, and set the $Button.Text (its name) as the name of the .exe, and then it sets the $Button.Tag as the file path, so when I go do the button.Add_Click , I just call the Button.Tag as it will have the path of my Exe.
Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName PresentationFramework
[System.Windows.Forms.Application]::EnableVisualStyles()
$Form = New-Object system.Windows.Forms.Form
$Form.ClientSize = '580,400'
$Form.Text = "Test"
$Form.TopMost = $false
$Form.FormBorderStyle = 'Fixed3D'
$Form.MaximizeBox = $false
$Form.minimumSize = New-Object System.Drawing.Size(580,400)
$Form.maximumSize = New-Object System.Drawing.Size(580,400)
#Place Holder Form Junk Above
#Reset these on Run
$Global:x = 10 #Reset up down
$Global:z = 10 #Reset left right
$Global:ObjectNumber = -1 #Reset Object Count
Function Make-Button([string] $ToolName, [string] $ToolPath, [string] $SetZ, [string] $SetX){
$Button = New-Object system.Windows.Forms.Button
$Button.text = $ToolName
$Button.width = 120
$Button.height = 120
$Button.location = New-Object System.Drawing.Point($SetZ,$SetX)
$Button.Font = New-Object System.Drawing.Font('Franklin Gothic',10)
$Button.FlatStyle = [System.Windows.Forms.FlatStyle]::Flat
$Button.FlatAppearance.BorderSize = 0
$Button.ForeColor = [System.Drawing.ColorTranslator]::FromHtml("#ffffff")
$Button.BackColor = [System.Drawing.ColorTranslator]::FromHtml("#515582")
$Button.tag = $ToolPath #<- this is where the answer was. Throwing my desired callback into an unused property of the the Button. in this case, i used _.Tag
$Button.Add_Click{start $this.tag}
$Form.Controls.AddRange(#($Button))
Write-Host "$ToolName"
Write-Host "$ToolPath"
Write-Host "$SetZ"
Write-Host "$SetX"
}
function Get-Position{
switch ($Global:ObjectNumber) {
-1{$Global:ObjectNumber += 1
Write-Host "Object:" $Global:ObjectNumber
$Global:x = 0
$Global:z += 0}
Default{$Global:ObjectNumber += 1
Write-Host "Object:" $Global:ObjectNumber
$Global:x += 0
$Global:z += 140}
}#end switch
if($Global:z -eq 570){ #Make New Row
$Global:x += 140
$Global:z = 10
Write-Host "New Row"
}
}
$Tools = Get-ChildItem "C:\WINDOWS\system32" -Filter *.exe
$Count = ( $Tools | Measure-Object ).Count;
Write-Host "Entries:" $Count
$Names = #($Tools) #Put Tools in Array
$Names | ForEach-Object{
Get-Position
Make-Button ($_.Name).replace(".exe","") ($_.FullName) ($z) ($x)
}
#End Form
$Test.Add_Shown( {$Test.Activate()})
$Test.ShowDialog()
[void]$Form.ShowDialog()
Continuing from my comment...
A small refactor to get this to show where things are
Clear-Host
Add-Type -AssemblyName System.Windows.Forms,
PresentationFramework
[System.Windows.Forms.Application]::EnableVisualStyles()
$Form = New-Object system.Windows.Forms.Form
$Form.text = 'Form'
$Form.TopMost = $false
$Form.ClientSize = New-Object System.Drawing.Point(760,400)
$Form.minimumSize = New-Object System.Drawing.Size(760,400)
$Form.maximumSize = New-Object System.Drawing.Size(760,400)
$GetCSV = Import-Csv -LiteralPath 'D:\Scripts\File.csv'
$Details = $GetCSV.Name
$DeviceList = $GetCSV
$Count = $DeviceList.Count
$ObjectNumber = -1
"Total Entries: $Count`n`n"
$ObjDown = 0
$ObjRight = 0
$DeviceList.Name |
ForEach-Object{
$ObjectNumber += 1
"`nObject: $ObjectNumber"
$x = 0
$ObjRight = 120
if($ObjRight -eq 720)
{
$x = 120
$ObjRight = 0
'New Row'
}
"x $x"
"z $ObjRight"
$ButtonLabel = ($GetCSV[$ObjectNumber]).Name
set-Variable -Name $("var$ObjectNumber") -Value ($GetCSV[$ObjectNumber] |
Select Name, Type, Link, Script, File, FileSource)
("Name: $((Get-Variable -Name $("var$ObjectNumber") -ValueOnly).Name)")
("Type: $((Get-Variable -Name $("var$ObjectNumber") -ValueOnly).Type)")
("Link: $((Get-Variable -Name $("var$ObjectNumber") -ValueOnly).Link)")
("Script: $((Get-Variable -Name $("var$ObjectNumber") -ValueOnly).Script)")
("File: $((Get-Variable -Name $("var$ObjectNumber") -ValueOnly).File)")
$PSitem = New-Object system.Windows.Forms.Button
$PSitem.text = $ButtonLabel
$PSitem.width = 100
$PSitem.height = 100
$PSitem.location = New-Object System.Drawing.Point($ObjRight,$x)
$PSitem.Font = New-Object System.Drawing.Font('Microsoft Sans Serif',10)
$PSitem.Add_Click({
$(Get-Variable -Name $("var$ObjectNumber") -ValueOnly)
})
$Form.Controls.Add($PSitem)
}
#[void]$Form.ShowDialog()
Here is an example I gave as an answer to another post to dynamically create UX/UI elements and assign a form event, though not using an external file, it's the same concept.
How to create multiple button with PowerShell?
Add tooltip and form event, like so...
$Form = New-Object system.Windows.Forms.Form
$Form.ClientSize = New-Object System.Drawing.Point(381,316)
$Form.text = "Auto Button UI"
$Form.TopMost = $false
$Form.BackColor = [System.Drawing.ColorTranslator]::FromHtml("#c9f6fe")
$i = 0
Get-Variable -Name 'Button*' |
Remove-Variable
$objTooltip = New-Object System.Windows.Forms.ToolTip
$objTooltip.InitialDelay = 100
1..3 |
foreach{
$CurrentButton = $null
$CurrentButton = New-Object System.Windows.Forms.Button
$CurrentButton.Location = "$(50+100*$i), 275"
$CurrentButton.Text = $PSItem
$CurrentButton.Font = New-Object System.Drawing.Font('Microsoft Sans Serif',10)
New-Variable "Button$PSitem" $CurrentButton
$objTooltip.SetToolTip(
$CurrentButton,
"Execute action assigned to $($CurrentButton.Text)"
)
$CurrentButton.add_click(
{
[System.Windows.Forms.MessageBox]::
Show(
"$($CurrentButton.Text)", $($CurrentButton.Text), [System.Windows.Forms.MessageBoxButtons]::
OKCancel, [System.Windows.Forms.MessageBoxIcon]::Information
)
})
$i++
$form.Controls.Add($CurrentButton)
}
[void]$Form.ShowDialog()
Yet, though it adds the event to each button element, the message text is the last one passed. Unless explicitly called as in the example from the link.
To adapt the second example in the answer already provided here so that the message text is not just the last one passed, you can change the reference within the event to the instance this.text rather than the iteratively updated $CurrentButton.text
$CurrentButton.add_click(
{
[System.Windows.Forms.MessageBox]::
Show(
"$($this.Text)", $($this.Text), [System.Windows.Forms.MessageBoxButtons]::
OKCancel, [System.Windows.Forms.MessageBoxIcon]::Information
)
})
Credit to jrv https://social.technet.microsoft.com/Forums/ie/en-US/09ff4141-6222-4bff-b8a9-a1253e0d378a/powershell-form-procedurally-creating-buttons?forum=ITCG
Full code with serialization of button object and event:
Clear-Host
Add-Type -AssemblyName System.Windows.Forms,
PresentationFramework
[System.Windows.Forms.Application]::EnableVisualStyles()
$Form = New-Object system.Windows.Forms.Form
$Form.ClientSize = New-Object System.Drawing.Point(381,316)
$Form.text = "Auto Button UI"
$Form.TopMost = $false
$Form.BackColor = [System.Drawing.ColorTranslator]::FromHtml("#c9f6fe")
$i = 0
Get-Variable -Name 'Button*' |
Remove-Variable
$objTooltip = New-Object System.Windows.Forms.ToolTip
$objTooltip.InitialDelay = 100
1..3 |
foreach{
$CurrentButton = $null
$CurrentButton = New-Object System.Windows.Forms.Button
$CurrentButton.Location = "$(50+100*$i), 275"
$CurrentButton.Text = $PSitem
$CurrentButton.Font = New-Object System.Drawing.Font('Microsoft Sans Serif',10)
New-Variable "Button$PSitem" $CurrentButton
$objTooltip.SetToolTip(
$CurrentButton,
"Execute action assigned to $($CurrentButton.Text)"
)
$CurrentButton.add_click(
{
[System.Windows.Forms.MessageBox]::
Show(
"$($this.Text)", $($this.Text), [System.Windows.Forms.MessageBoxButtons]::
OKCancel, [System.Windows.Forms.MessageBoxIcon]::Information
)
})
$i++
$form.Controls.Add($CurrentButton)
}
[void]$Form.ShowDialog()
I have code that will start process in tray (task bar). After right click on tray icon it will show menu. After clicking on first menu item winform window starts. This winform shows status about the notepad process. My goal is to change the tray icon based on the status of notepad (if notepad is running then show online.ico otherwise show offline.ico). If I understand correct then my code is starting/stopping System.Windows.Forms.Timer every time winform window is opened/closed and I'm not sure if this is the best possible approach. My guess is that I need to somehow start timer "outside" of OnMenuItem1ClickEventFn so it can somehow reload *.ico files. Following script is heavily inspired by this site:
Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing
function Test-Notepad {
[bool](Get-Process -Name 'notepad' -ErrorAction SilentlyContinue)
}
function OnMenuItem1ClickEventFn () {
# Build Label object
$Label = New-Object System.Windows.Forms.Label
$Label.Name = "labelName"
$Label.AutoSize = $True
# Set and start timer
$timer = New-Object System.Windows.Forms.Timer
$timer.Interval = 1000
$timer.Add_Tick({
if ($Label){
$Label.Text = if (Test-Notepad) { "Notepad is running" } else { "Notepad is NOT running" }
}
})
$timer.Start()
# Build Form object
$Form = New-Object System.Windows.Forms.Form
$Form.Text = "My Form"
$Form.Size = New-Object System.Drawing.Size(200,200)
$Form.StartPosition = "CenterScreen"
$Form.Topmost = $True
$Form.Add_Closing({ $timer.Dispose() }) # Dispose() also stops the timer.
$Form.Controls.Add($Label) # Add label to form
$form.ShowDialog()| Out-Null # Show the Form
}
function OnMenuItem4ClickEventFn () {
$Main_Tool_Icon.Visible = $false
$window.Close()
Stop-Process $pid
}
function create_taskbar_menu{
# Create menu items
$Main_Tool_Icon = New-Object System.Windows.Forms.NotifyIcon
$Main_Tool_Icon.Text = "Icon Text"
$Main_Tool_Icon.Icon = $icon
$Main_Tool_Icon.Visible = $true
$MenuItem1 = New-Object System.Windows.Forms.MenuItem
$MenuItem1.Text = "Menu Item 1"
$MenuItem2 = New-Object System.Windows.Forms.MenuItem
$MenuItem2.Text = "Menu Item 2"
$MenuItem3 = New-Object System.Windows.Forms.MenuItem
$MenuItem3.Text = "Menu Item 3"
$MenuItem4 = New-Object System.Windows.Forms.MenuItem
$MenuItem4.Text = "Exit"
# Add menu items to context menu
$contextmenu = New-Object System.Windows.Forms.ContextMenu
$Main_Tool_Icon.ContextMenu = $contextmenu
$Main_Tool_Icon.contextMenu.MenuItems.AddRange($MenuItem1)
$Main_Tool_Icon.contextMenu.MenuItems.AddRange($MenuItem2)
$Main_Tool_Icon.contextMenu.MenuItems.AddRange($MenuItem3)
$Main_Tool_Icon.contextMenu.MenuItems.AddRange($MenuItem4)
$MenuItem4.add_Click({OnMenuItem4ClickEventFn})
$MenuItem1.add_Click({OnMenuItem1ClickEventFn})
}
$Current_Folder = split-path $MyInvocation.MyCommand.Path
# Add assemblies for WPF and Mahapps
[System.Reflection.Assembly]::LoadWithPartialName('System.Windows.Forms') | out-null
[System.Reflection.Assembly]::LoadWithPartialName('presentationframework') | out-null
[System.Reflection.Assembly]::LoadWithPartialName('System.Drawing') | out-null
[System.Reflection.Assembly]::LoadWithPartialName('WindowsFormsIntegration') | out-null
# [System.Reflection.Assembly]::LoadFrom("Current_Folder\assembly\MahApps.Metro.dll") | out-null
# Choose an icon to display in the systray
$icon = [System.Drawing.Icon]::ExtractAssociatedIcon("$Current_Folder/icons/online.ico")
# use this icon when notepad is not running
# $icon = [System.Drawing.Icon]::ExtractAssociatedIcon("$Current_Folder/icons/offline.ico")
create_taskbar_menu
# Make PowerShell Disappear - Thanks Chrissy
$windowcode = '[DllImport("user32.dll")] public static extern bool ShowWindowAsync(IntPtr hWnd, int nCmdShow);'
$asyncwindow = Add-Type -MemberDefinition $windowcode -name Win32ShowWindowAsync -namespace Win32Functions -PassThru
$null = $asyncwindow::ShowWindowAsync((Get-Process -PID $pid).MainWindowHandle, 0)
# Use a Garbage colection to reduce Memory RAM
# https://dmitrysotnikov.wordpress.com/2012/02/24/freeing-up-memory-in-powershell-using-garbage-collector/
# https://learn.microsoft.com/fr-fr/dotnet/api/system.gc.collect?view=netframework-4.7.2
[System.GC]::Collect()
# Create an application context for it to all run within - Thanks Chrissy
# This helps with responsiveness, especially when clicking Exit - Thanks Chrissy
$appContext = New-Object System.Windows.Forms.ApplicationContext
[void][System.Windows.Forms.Application]::Run($appContext)
EDITED: working solution based on #BACON answer
# Toggle following two lines
Set-StrictMode -Version Latest
# Set-StrictMode -Off
Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing
function Test-Notepad {
[bool](Get-Process -Name 'notepad' -ErrorAction SilentlyContinue)
}
function OnMenuItem1ClickEventFn () {
# Build Form object
$Form = New-Object System.Windows.Forms.Form
$Form.Text = "My Form"
$Form.Size = New-Object System.Drawing.Size(200,200)
$Form.StartPosition = "CenterScreen"
$Form.Topmost = $True
$Form.Controls.Add($Label) # Add label to form
$form.ShowDialog()| Out-Null # Show the Form
}
function OnMenuItem4ClickEventFn () {
$Main_Tool_Icon.Visible = $false
[System.Windows.Forms.Application]::Exit()
}
function create_taskbar_menu{
# Create menu items
$MenuItem1 = New-Object System.Windows.Forms.MenuItem
$MenuItem1.Text = "Menu Item 1"
$MenuItem2 = New-Object System.Windows.Forms.MenuItem
$MenuItem2.Text = "Menu Item 2"
$MenuItem3 = New-Object System.Windows.Forms.MenuItem
$MenuItem3.Text = "Menu Item 3"
$MenuItem4 = New-Object System.Windows.Forms.MenuItem
$MenuItem4.Text = "Exit"
# Add menu items to context menu
$contextmenu = New-Object System.Windows.Forms.ContextMenu
$Main_Tool_Icon.ContextMenu = $contextmenu
$Main_Tool_Icon.contextMenu.MenuItems.AddRange($MenuItem1)
$Main_Tool_Icon.contextMenu.MenuItems.AddRange($MenuItem2)
$Main_Tool_Icon.contextMenu.MenuItems.AddRange($MenuItem3)
$Main_Tool_Icon.contextMenu.MenuItems.AddRange($MenuItem4)
$MenuItem4.add_Click({OnMenuItem4ClickEventFn})
$MenuItem1.add_Click({OnMenuItem1ClickEventFn})
}
$Current_Folder = split-path $MyInvocation.MyCommand.Path
# Add assemblies for WPF and Mahapps
[System.Reflection.Assembly]::LoadWithPartialName('System.Windows.Forms') | out-null
[System.Reflection.Assembly]::LoadWithPartialName('presentationframework') | out-null
[System.Reflection.Assembly]::LoadWithPartialName('System.Drawing') | out-null
[System.Reflection.Assembly]::LoadWithPartialName('WindowsFormsIntegration') | out-null
# [System.Reflection.Assembly]::LoadFrom("Current_Folder\assembly\MahApps.Metro.dll") | out-null
# Choose an icon to display in the systray
$onlineIcon = [System.Drawing.Icon]::ExtractAssociatedIcon("$Current_Folder/icons/online.ico")
# use this icon when notepad is not running
$offlineIcon = [System.Drawing.Icon]::ExtractAssociatedIcon("$Current_Folder/icons/offline.ico")
$Main_Tool_Icon = New-Object System.Windows.Forms.NotifyIcon
$Main_Tool_Icon.Text = "Icon Text"
$Main_Tool_Icon.Icon = if (Test-Notepad) { $onlineIcon } else { $offlineIcon }
$Main_Tool_Icon.Visible = $true
# Build Label object
$Label = New-Object System.Windows.Forms.Label
$Label.Name = "labelName"
$Label.AutoSize = $True
# Initialize the timer
$timer = New-Object System.Windows.Forms.Timer
$timer.Interval = 1000
$timer.Add_Tick({
if ($Label){
$Label.Text, $Main_Tool_Icon.Icon = if (Test-Notepad) {
"Notepad is running", $onlineIcon
} else {
"Notepad is NOT running", $offlineIcon
}
}
})
$timer.Start()
create_taskbar_menu
# Make PowerShell Disappear - Thanks Chrissy
$windowcode = '[DllImport("user32.dll")] public static extern bool ShowWindowAsync(IntPtr hWnd, int nCmdShow);'
$asyncwindow = Add-Type -MemberDefinition $windowcode -name Win32ShowWindowAsync -namespace Win32Functions -PassThru
$null = $asyncwindow::ShowWindowAsync((Get-Process -PID $pid).MainWindowHandle, 0)
# Use a Garbage colection to reduce Memory RAM
# https://dmitrysotnikov.wordpress.com/2012/02/24/freeing-up-memory-in-powershell-using-garbage-collector/
# https://learn.microsoft.com/fr-fr/dotnet/api/system.gc.collect?view=netframework-4.7.2
[System.GC]::Collect()
# Create an application context for it to all run within - Thanks Chrissy
# This helps with responsiveness, especially when clicking Exit - Thanks Chrissy
$appContext = New-Object System.Windows.Forms.ApplicationContext
try
{
[System.Windows.Forms.Application]::Run($appContext)
}
finally
{
foreach ($component in $timer, $Main_Tool_Icon, $offlineIcon, $onlineIcon, $appContext)
{
# The following test returns $false if $component is
# $null, which is really what we're concerned about
if ($component -is [System.IDisposable])
{
$component.Dispose()
}
}
Stop-Process -Id $PID
}
You already have code to set the icon used by the NotifyIcon...
$Main_Tool_Icon.Icon = $icon
...and to define the icons to use...
# Choose an icon to display in the systray
$icon = [System.Drawing.Icon]::ExtractAssociatedIcon("$Current_Folder/icons/online.ico")
# use this icon when notepad is not running
# $icon = [System.Drawing.Icon]::ExtractAssociatedIcon("$Current_Folder/icons/offline.ico")
...and to periodically test if Notepad is running and respond appropriately...
$timer.Add_Tick({
if ($Label){
$Label.Text = if (Test-Notepad) { "Notepad is running" } else { "Notepad is NOT running" }
}
})
You just need to combine them with a couple additional tweaks...
Store each icon in its own variable with a descriptive name so its easy to switch between them.
$Main_Tool_Icon needs to be defined outside the scope of create_taskbar_menu so it can be accessed inside of OnMenuItem1ClickEventFn. I moved the creation and initialization to just before the call to create_taskbar_menu, but you could also initialize it inside of create_taskbar_menu or some other function.
That ends up looking like this...
# Choose an icon to display in the systray
$onlineIcon = [System.Drawing.Icon]::ExtractAssociatedIcon("$Current_Folder/icons/online.ico")
# use this icon when notepad is not running
$offlineIcon = [System.Drawing.Icon]::ExtractAssociatedIcon("$Current_Folder/icons/offline.ico")
$Main_Tool_Icon = New-Object System.Windows.Forms.NotifyIcon
$Main_Tool_Icon.Text = "Icon Text"
$Main_Tool_Icon.Icon = if (Test-Notepad) { $onlineIcon } else { $offlineIcon }
$Main_Tool_Icon.Visible = $true
create_taskbar_menu
...and this...
$timer.Add_Tick({
if ($Label){
# Change the text and icon with one test
$Label.Text, $Main_Tool_Icon.Icon = if (Test-Notepad) {
"Notepad is running", $onlineIcon
} else {
"Notepad is NOT running", $offlineIcon
}
}
})
You'll see that an icon is selected based on the result of Test-Notepad both when $Main_Tool_Icon is initialized and when the Tick event is raised.
As for disposing $timer when $Form is closing...
$Form.Add_Closing({ $timer.Dispose() })
...that is almost an appropriate place to do that, however...
The Closing event is raised essentially when the form has been requested to close; it provides an opportunity for that request to be canceled. The Closed event would be more appropriate because it is raised when the form has actually been closed.
The documentation states that both the Closing and Closed events are obsolete. Use the FormClosed event instead.
Also, in OnMenuItem4ClickEventFn you are calling $window.Close() even though $window is not defined; I think you meant $Form.Close(). An alternative that I think is a bit cleaner would be to have clicking the Exit menu item merely exit the Windows Forms application loop...
function OnMenuItem4ClickEventFn () {
$Main_Tool_Icon.Visible = $false
[System.Windows.Forms.Application]::Exit()
}
...and then you can put your cleanup/teardown code in a finally block at the end of the script...
try
{
# This call returns when [System.Windows.Forms.Application]::Exit() is called
[System.Windows.Forms.Application]::Run($appContext)
}
finally
{
# $timer would also have to be defined at the script scope
# (outside of OnMenuItem1ClickEventFn) for this to work
$timer.Dispose()
# $Form, $Label, $Main_Tool_Icon, $onlineIcon, etc. would all be candidates for disposal...
# Exit the entire PowerShell process
Stop-Process $pid
}
The following is complete code that incorporates the changes mentioned above and works for me...
Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing
function Test-Notepad {
[bool](Get-Process -Name 'notepad' -ErrorAction SilentlyContinue)
}
function OnMenuItem1ClickEventFn () {
$timer.Start()
# Build Form object
$Form = New-Object System.Windows.Forms.Form
$Form.Text = "My Form"
$Form.Size = New-Object System.Drawing.Size(200,200)
$Form.StartPosition = "CenterScreen"
$Form.Topmost = $True
$Form.Add_Closing({ $timer.Dispose() }) # Dispose() also stops the timer.
$Form.Controls.Add($Label) # Add label to form
$form.ShowDialog()| Out-Null # Show the Form
}
function OnMenuItem4ClickEventFn () {
$Main_Tool_Icon.Visible = $false
[System.Windows.Forms.Application]::Exit()
}
function create_taskbar_menu{
# Create menu items
$MenuItem1 = New-Object System.Windows.Forms.MenuItem
$MenuItem1.Text = "Menu Item 1"
$MenuItem2 = New-Object System.Windows.Forms.MenuItem
$MenuItem2.Text = "Menu Item 2"
$MenuItem3 = New-Object System.Windows.Forms.MenuItem
$MenuItem3.Text = "Menu Item 3"
$MenuItem4 = New-Object System.Windows.Forms.MenuItem
$MenuItem4.Text = "Exit"
# Add menu items to context menu
$contextmenu = New-Object System.Windows.Forms.ContextMenu
$Main_Tool_Icon.ContextMenu = $contextmenu
$Main_Tool_Icon.contextMenu.MenuItems.AddRange($MenuItem1)
$Main_Tool_Icon.contextMenu.MenuItems.AddRange($MenuItem2)
$Main_Tool_Icon.contextMenu.MenuItems.AddRange($MenuItem3)
$Main_Tool_Icon.contextMenu.MenuItems.AddRange($MenuItem4)
$MenuItem4.add_Click({OnMenuItem4ClickEventFn})
$MenuItem1.add_Click({OnMenuItem1ClickEventFn})
}
$Current_Folder = split-path $MyInvocation.MyCommand.Path
# Add assemblies for WPF and Mahapps
[System.Reflection.Assembly]::LoadWithPartialName('System.Windows.Forms') | out-null
[System.Reflection.Assembly]::LoadWithPartialName('presentationframework') | out-null
[System.Reflection.Assembly]::LoadWithPartialName('System.Drawing') | out-null
[System.Reflection.Assembly]::LoadWithPartialName('WindowsFormsIntegration') | out-null
# [System.Reflection.Assembly]::LoadFrom("Current_Folder\assembly\MahApps.Metro.dll") | out-null
# Choose an icon to display in the systray
$onlineIcon = [System.Drawing.Icon]::ExtractAssociatedIcon("$Current_Folder/icons/online.ico")
# use this icon when notepad is not running
$offlineIcon = [System.Drawing.Icon]::ExtractAssociatedIcon("$Current_Folder/icons/offline.ico")
$Main_Tool_Icon = New-Object System.Windows.Forms.NotifyIcon
$Main_Tool_Icon.Text = "Icon Text"
$Main_Tool_Icon.Icon = if (Test-Notepad) { $onlineIcon } else { $offlineIcon }
$Main_Tool_Icon.Visible = $true
# Build Label object
$Label = New-Object System.Windows.Forms.Label
$Label.Name = "labelName"
$Label.AutoSize = $True
# Initialize the timer
$timer = New-Object System.Windows.Forms.Timer
$timer.Interval = 1000
$timer.Add_Tick({
if ($Label){
$Label.Text, $Main_Tool_Icon.Icon = if (Test-Notepad) {
"Notepad is running", $onlineIcon
} else {
"Notepad is NOT running", $offlineIcon
}
}
})
create_taskbar_menu
# Make PowerShell Disappear - Thanks Chrissy
$windowcode = '[DllImport("user32.dll")] public static extern bool ShowWindowAsync(IntPtr hWnd, int nCmdShow);'
$asyncwindow = Add-Type -MemberDefinition $windowcode -name Win32ShowWindowAsync -namespace Win32Functions -PassThru
$null = $asyncwindow::ShowWindowAsync((Get-Process -PID $pid).MainWindowHandle, 0)
# Use a Garbage colection to reduce Memory RAM
# https://dmitrysotnikov.wordpress.com/2012/02/24/freeing-up-memory-in-powershell-using-garbage-collector/
# https://learn.microsoft.com/fr-fr/dotnet/api/system.gc.collect?view=netframework-4.7.2
[System.GC]::Collect()
# Create an application context for it to all run within - Thanks Chrissy
# This helps with responsiveness, especially when clicking Exit - Thanks Chrissy
$appContext = New-Object System.Windows.Forms.ApplicationContext
try
{
[System.Windows.Forms.Application]::Run($appContext)
}
finally
{
foreach ($component in $timer, $form, $Main_Tool_Icon, $offlineIcon, $onlineIcon, $appContext)
{
# The following test returns $false if $component is
# $null, which is really what we're concerned about
if ($component -is [System.IDisposable])
{
$component.Dispose()
}
}
Stop-Process -Id $PID
}
I am struggling with an until loop in combination with a random picker and a mouse click in a PowerShell Form.
I am able to run a random picker without the form , where I have weekdays, picked by random and one by one day, is being removed, until the arraylist is empty. Works not bad.
$Weekdays = 'Monday','Tuesday','Wednesday','Thursday','Friday','Saturday','Sunday'
[System.Collections.ArrayList]$arraylist = $Weekdays
Write-Host $arraylist -ForegroundColor Green
pause
do {
$removetask = Get-Random $arraylist.ToArray()
$arraylist.Remove($removetask)
Write-Host $removetask
Write-Host $arraylist -ForegroundColor Red
pause
} until ($arraylist.Count -eq 0)
In another approach, I tried to do the same, but this time, I want to control the looping itself, that as soon as the first key from the arraylist is taken and shown in a label, I have to click the mouse button, so it continues to take the next random.
Without the do {} until () I got so far:
$TestForm = New-Object System.Windows.Forms.Form
$TestForm.Size = New-Object System.Drawing.Size (1200,800)
$TestForm.Text ='Random Test'
$TestForm.StartPosition = "CenterScreen"
$TestForm.AutoSize = $true
$TestForm.BringToFront()
$TestForm.BackgroundImageLayout = "Stretch"
[System.Collections.ArrayList]$Weekdays = 'Monday','Tuesday','Wednesday','Thursday','Friday','Saturday','Sunday'
$TestLabel = New-Object System.Windows.Forms.label
$TestLabel.Location = New-Object System.Drawing.Size '500,200'
$TestLabel.Size = New-Object System.Drawing.Size '600,60'
$TestLabel.Font = New-Object System.Drawing.Font ('Times New Roman','20',[System.Drawing.FontStyle]::Bold)
$TestLabel.BackColor = 'Transparent'
$TestLabel.ForeColor = "Blue"
$removetask = Get-Random $Weekdays.ToArray()
$TestLabel.Text = $removetask
$TestForm.Controls.Add($TestLabel)
$TestButton = New-Object System.Windows.Forms.Button
$TestButton.Location = New-Object System.Drawing.Size '500,600'
$TestButton.Size = New-Object System.Drawing.Size '200,75'
$TestButton.Font = New-Object System.Drawing.Font ('Arial','10',[System.Drawing.FontStyle]::Bold)
$TestButton.Text = 'Next Random'
$TestForm.Controls.Add($TestButton)
$TestButton.Add_Click()
$TestForm.ShowDialog()
$TestForm.Dispose()
Now I have a few lines of code left, I am not able to include on such way, so it works on the following way.
The testform opens, and in a label I see chosen by random one of the weekdays. Clicking next will remove the chosen weekday from the arraylist and show the next weekday by random and will continue until the arraylist is empty.
The missing pieces to the puzzle are:
### The loop itself
do {} until ()
### code to find a Random value from $weekdays and write it into $removetask
$removetask = Get-Random $Weekdays.ToArray()
### code to remove the randomly chosen day and remove it from the arraylist
$Weekdays.Remove($removetask)
#### check if array is empty
($weekdays.Count -eq 0)
I was playing around with the codes and tried with Button.Add_Click() and also with ButtonClickEvent {} but either, the loop is not running, the counter is not working correctly or I somehow messed up the code on such way, that it is stuck somewhere, that not even the form is being shown.
The following enhanced adjustment of your script implements some kind of a loop in the form.
Note that no loop keywords (like do, while, until) and even no if keyword are used:
### Load Assemblies for creating form & controls ###
if ( -not ("System.Windows.Forms.Form" -as [type]) ) {
Add-Type -AssemblyName System.Windows.Forms
}
if ( -not ("System.Drawing.Font" -as [type]) ) {
Add-Type -AssemblyName System.Drawing
}
$TestForm = New-Object System.Windows.Forms.Form
$TestForm.Size = New-Object System.Drawing.Size (1200,800)
$TestForm.Text ='Random Test'
$TestForm.StartPosition = "CenterScreen"
$TestForm.AutoSize = $true
$TestForm.BringToFront()
$TestForm.BackgroundImageLayout = "Stretch"
$TestLabel = New-Object System.Windows.Forms.label
$TestLabel.Location = New-Object System.Drawing.Size '500,200'
$TestLabel.Size = New-Object System.Drawing.Size '600,60'
$TestLabel.Font = New-Object System.Drawing.Font ('Times New Roman','20',[System.Drawing.FontStyle]::Bold)
$TestLabel.BackColor = 'Transparent'
$TestLabel.ForeColor = "Blue"
$TestForm.Controls.Add($TestLabel)
$TestLabe2 = New-Object System.Windows.Forms.Label
$TestLabe2.Location = New-Object System.Drawing.Size '200,300'
$TestLabe2.Size = New-Object System.Drawing.Size '900,200'
$TestLabe2.Font = New-Object System.Drawing.Font ([System.Windows.Forms.Label]::DefaultFont.Name,'16',[System.Drawing.FontStyle]::Italic)
$TestLabe2.BackColor = 'Transparent'
$TestLabe2.ForeColor = [System.Drawing.Color]::MidnightBlue
$TestForm.Controls.Add($TestLabe2)
$TestButton = New-Object System.Windows.Forms.Button
$TestButton.Location = New-Object System.Drawing.Size '500,600'
$TestButton.Size = New-Object System.Drawing.Size '200,75'
$TestButton.Font = New-Object System.Drawing.Font ('Arial','10',[System.Drawing.FontStyle]::Bold)
$TestButton.Text = 'Next Random'
$TestForm.Controls.Add($TestButton)
$TestButtoX = New-Object System.Windows.Forms.Button
$TestButtoX.Location = New-Object System.Drawing.Size '200,600'
$TestButtoX.Size = New-Object System.Drawing.Size '200,75'
$TestButtoX.Font = New-Object System.Drawing.Font ('Arial','10',[System.Drawing.FontStyle]::Bold)
$TestButtoX.Text = 'Next Round'
$TestButtoX.Enabled = $false
$TestForm.Controls.Add($TestButtoX)
Function Swap-Buttons {
$TestButton.Enabled = [bool]$script:Weekdays.Count
$TestButtoX.Enabled = -not [bool]$script:Weekdays.Count
}
Function RemoveWeekday {
$script:removetask = Get-Random $script:Weekdays.ToArray()
$script:Weekdays.Remove($script:removetask)
$TestLabe2.Text = ('(remain {0})' -f $script:Weekdays.Count), ($script:Weekdays -join ', ') -join ': '
$TestLabel.Text = $script:removetask
Swap-Buttons
}
Function DefineWeek {
$script:Weekdays = [System.Collections.ArrayList]([System.Enum]::GetNames([System.DayOfWeek]))
<#
# debugging: try another array list (a larger one)
$script:Weekdays = [System.Collections.ArrayList]([System.Drawing.Color] |
Get-Member -MemberType Properties -Static -Force |
Where-Object Name -match ".+blue" |
Select-Object -ExpandProperty Name
)
<##>
}
$TestButton.Add_Click({
RemoveWeekday
})
$TestButtoX.Add_Click({
DefineWeek
$TestButtoX.Enabled = $false
$TestButton.Enabled = $true
RemoveWeekday
})
$script:removetask = ''
DefineWeek
RemoveWeekday
$TestForm.ShowDialog()
$TestForm.Dispose()
I have a custom form and I need to auto close the window on selection of selected part of the text displayed on the that form window
Show-OAuthWindow shows the dialogue box for user consent and I want to avoid using showdialog() and select the auto-selected text on the window
Function Show-OAuthWindow {
Add-Type -AssemblyName System.Windows.Forms
$form = New-Object -TypeName System.Windows.Forms.Form -Property #{Width=600;Height=800}
$web = New-Object -TypeName System.Windows.Forms.WebBrowser -Property #{Width=580;Height=780;Url=($url -f ($Scope -join "%20")) }
$DocComp = {
$Global:uri = $web.Url.AbsoluteUri
if ($Global:Uri -match "error=[^&]*|code=[^&]*") {$form.Close() }
}
$web.ScriptErrorsSuppressed = $true
$web.Add_DocumentCompleted($DocComp)
$form.Controls.Add($web)
$form.Add_Shown({$form.Activate()})
$form.ShowDialog() | Out-Null
}
I have the following PowerShell function which works well, but the window opens up in the background behind the PowerShell ISE.
# Shows folder browser dialog box and sets to variable
function Get-FolderName() {
Add-Type -AssemblyName System.Windows.Forms
$FolderBrowser = New-Object System.Windows.Forms.FolderBrowserDialog -Property #{
SelectedPath = 'C:\Temp\'
ShowNewFolderButton = $false
Description = "Select Staging Folder."
}
# If cancel is clicked the script will exit
if ($FolderBrowser.ShowDialog() -eq "Cancel") {break}
$FolderBrowser.SelectedPath
} #end function Get-FolderName
I can see there's a .TopMost property that can be used with the OpenFileDialog class but this doesn't appear to transfer over to the FolderBrowserDialog class.
Am I missing something?
Hope this helps
Add-Type -AssemblyName System.Windows.Forms
$FolderBrowser = New-Object System.Windows.Forms.FolderBrowserDialog
$FolderBrowser.Description = 'Select the folder containing the data'
$result = $FolderBrowser.ShowDialog((New-Object System.Windows.Forms.Form -Property #{TopMost = $true }))
if ($result -eq [Windows.Forms.DialogResult]::OK){
$FolderBrowser.SelectedPath
} else {
exit
}
//Edit to comment
There are 2 variants (overloads) of the ShowDialog () method.
See documentation: http://msdn.microsoft.com/en-us/library/system.windows.forms.openfiledialog.showdialog%28v=vs.110%29.aspx
In the second variant, you can specify the window that should be the mother of the dialogue.
Topmost should be used sparingly or not at all! If multiple windows are topmost then which is topmost? ;-))
First try to set your window as a mother then the OpenfileDialog / SaveFileDialog should always appear above your window:
$openFileDialog1.ShowDialog($form1)
If that's not enough, take Topmost.
Your dialogue window inherits the properties from the mother. If your mother window is topmost, then the dialog is also topmost.
Here is an example that sets the dialogue Topmost.
In this example, however, a new unbound window is used, so the dialog is unbound.
$openFileDialog1.ShowDialog((New - Object System.Windows.Forms.Form - Property #{TopMost = $true; TopLevel = $true}))
A reliable way of doing this is to add a piece of C# code to the function.
With that code, you can get a Windows handle that implements the IWin32Window interface. Using that handle in the ShowDialog function will ensure the dialog is displayed on top.
Function Get-FolderName {
# To ensure the dialog window shows in the foreground, you need to get a Window Handle from the owner process.
# This handle must implement System.Windows.Forms.IWin32Window
# Create a wrapper class that implements IWin32Window.
# The IWin32Window interface contains only a single property that must be implemented to expose the underlying handle.
$code = #"
using System;
using System.Windows.Forms;
public class Win32Window : IWin32Window
{
public Win32Window(IntPtr handle)
{
Handle = handle;
}
public IntPtr Handle { get; private set; }
}
"#
if (-not ([System.Management.Automation.PSTypeName]'Win32Window').Type) {
Add-Type -TypeDefinition $code -ReferencedAssemblies System.Windows.Forms.dll -Language CSharp
}
# Get the window handle from the current process
# $owner = New-Object Win32Window -ArgumentList ([System.Diagnostics.Process]::GetCurrentProcess().MainWindowHandle)
# Or write like this:
$owner = [Win32Window]::new([System.Diagnostics.Process]::GetCurrentProcess().MainWindowHandle)
# Or use the the window handle from the desktop
# $owner = New-Object Win32Window -ArgumentList (Get-Process -Name explorer).MainWindowHandle
# Or write like this:
# $owner = [Win32Window]::new((Get-Process -Name explorer).MainWindowHandle)
$FolderBrowser = New-Object System.Windows.Forms.FolderBrowserDialog -Property #{
SelectedPath = 'C:\Temp\'
ShowNewFolderButton = $false
Description = "Select Staging Folder."
}
# set the return value only if a selection was made
$result = $null
If ($FolderBrowser.ShowDialog($owner) -eq "OK") {
$result = $FolderBrowser.SelectedPath
}
# clear the dialog from memory
$FolderBrowser.Dispose()
return $result
}
Get-FolderName
You can also opt for using the Shell.Application object with something like this:
# Show an Open Folder Dialog and return the directory selected by the user.
function Get-FolderName {
[CmdletBinding()]
param (
[Parameter(Mandatory=$false, ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true, Position=0)]
[string]$Message = "Select a directory.",
[string]$InitialDirectory = [System.Environment+SpecialFolder]::MyComputer,
[switch]$ShowNewFolderButton
)
$browserForFolderOptions = 0x00000041 # BIF_RETURNONLYFSDIRS -bor BIF_NEWDIALOGSTYLE
if (!$ShowNewFolderButton) { $browserForFolderOptions += 0x00000200 } # BIF_NONEWFOLDERBUTTON
$browser = New-Object -ComObject Shell.Application
# To make the dialog topmost, you need to supply the Window handle of the current process
[intPtr]$handle = [System.Diagnostics.Process]::GetCurrentProcess().MainWindowHandle
# see: https://msdn.microsoft.com/en-us/library/windows/desktop/bb773205(v=vs.85).aspx
$folder = $browser.BrowseForFolder($handle, $Message, $browserForFolderOptions, $InitialDirectory)
$result = $null
if ($folder) {
$result = $folder.Self.Path
}
# Release and remove the used Com object from memory
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($browser) | Out-Null
[System.GC]::Collect()
[System.GC]::WaitForPendingFinalizers()
return $result
}
$folder = Get-FolderName
if ($folder) { Write-Host "You selected the directory: $folder" }
else { "You did not select a directory." }
I just found an easy way to get PowerShell's IWin32Window value so forms can be modal. Create a System.Windows.Forms.NativeWindow object and assign PowerShell's handle to it.
function Show-FolderBrowser
{
Param ( [Parameter(Mandatory=1)][string]$Title,
[Parameter(Mandatory=0)][string]$DefaultPath = $(Split-Path $psISE.CurrentFile.FullPath),
[Parameter(Mandatory=0)][switch]$ShowNewFolderButton)
$DefaultPath = UNCPath2Mapped -path $DefaultPath;
$FolderBrowser = new-object System.Windows.Forms.folderbrowserdialog;
$FolderBrowser.Description = $Title;
$FolderBrowser.ShowNewFolderButton = $ShowNewFolderButton;
$FolderBrowser.SelectedPath = $DefaultPath;
$out = $null;
$caller = [System.Windows.Forms.NativeWindow]::new()
$caller.AssignHandle([System.Diagnostics.Process]::GetCurrentProcess().MainWindowHandle)
if (($FolderBrowser.ShowDialog($caller)) -eq [System.Windows.Forms.DialogResult]::OK.value__)
{
$out = $FolderBrowser.SelectedPath;
}
#Cleanup Disposabe Objects
Get-Variable -ErrorAction SilentlyContinue -Scope 0 | Where-Object {($_.Value -is [System.IDisposable]) -and ($_.Name -notmatch "PS\s*")} | ForEach-Object {$_.Value.Dispose(); $_ | Clear-Variable -ErrorAction SilentlyContinue -PassThru | Remove-Variable -ErrorAction SilentlyContinue -Force;}
return $out;
}