One of my admins wanted a little script, that visualizes if a certain process is running. I managed to accomplish this by creating a systray icon which visually changes according to the status. Now my problem is that I don't have a lot of experience working with GUIs and I don't seem to be able to exit the program when a certain button is clicked.
Sourcecode is here:
#Declare Assemblies
[System.Reflection.Assembly]::LoadWithPartialName('System.Window.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
#Add an icon to the systray
$bitmap_green = [System.Drawing.Bitmap]::FromFile('C:\Temp\green.png')
$hicon_green= $bitmap_green.GetHicon()
$icon_green = [system.drawing.icon]::FromHandle($hicon_green)
$bitmap_red = [System.Drawing.Bitmap]::FromFile('C:\Temp\red.png')
$hicon_red= $bitmap_red.GetHicon()
$icon_red = [system.drawing.icon]::FromHandle($hicon_red)
#Create Systray object
$Systray_Tool_Icon = New-Object System.Windows.Forms.NotifyIcon
#Mouseover text
$Systray_Tool_Icon.Text = "MOBS läuft"
$Systray_Tool_Icon.Icon = $icon_green
$Systray_Tool_Icon.Visible = $true
$Menu_Exit = New-Object System.Windows.Forms.MenuItem
$Menu_Exit.Text = "Exit"
$exit = $true
$contextmenu = New-Object System.Windows.Forms.ContextMenu
$Systray_Tool_Icon.ContextMenu = $contextmenu
$Systray_Tool_Icon.ContextMenu.MenuItems.AddRange($Menu_Exit)
$Menu_Exit.add_click({
$Systray_Tool_Icon.Visible = $false
$exit = $false
})
do {
$running = Get-Process mobsync.exe -ErrorAction SilentlyContinue
if (!$running)
{
$Systray_Tool_Icon.Icon = $icon_red
$Systray_Tool_Icon.Text = "MOBS läuft nicht"
}
else {
$Systray_Tool_Icon.Icon = $icon_green
$Systray_Tool_Icon.Text = "MOBS läuft "
}
[System.Windows.Forms.Application]::DoEvents()
} while ( $exit -eq $true)
My idea was to create a do while loop which continuesly runs until the admin presses a button in the contextmenu of the icon. So in essence I built an infinite loop.
My problem is, that the add_click isn't working as I expected. Can somebody give me some advice or some hints on how to properly close my loop on the click event?
Thanks in advance.
Related
I am attempting to create a form in Powershell. It contains a ComboBox dropdown option that I am using as a required field. Until an option is selected, the continue button will be disabled. This is the code for the ComboBox and the button enabling:
$TSTypeBox.Name = "TSType"
$TSTypeBox.Location = New-Object System.Drawing.Point(116,100)
$TSTypeBox.Size = New-Object System.Drawing.Size(145,20)
$TSTypeBox.add_MouseHover($ShowHelp)
$TSTypeBox.DropDownStyle = "DropDownList"
Foreach ($item in ("1","2","3","4","5")) {
$TSTypeBox.Items.Add($item) | Out-Null
}
$TSTypeBox.SelectedItem = $TSLocation
$handler_TSTypeBox_SelectedIndexChanged= {
If (($TSTypeBox.Text) -and ($ComputerNameBox.Text))
{
$OKButton.Enabled = 1
}
Else
{
$OKButton.Enabled = 0
}
}
$TSTypeBox.add_SelectedIndexChanged($handler_TSTypeBox_SelectedIndexChanged)
This code in particular works as intended so I'm not worried about that. I am here about the $TSTypeBox.SelectedItem = $TSLocation line that I included. I have code elsewhere that pulls the IP address of the computer the program is being run on, which is then matched against an if/elseif/else statement to determine if the computer belongs to 1 or to 2, which are options that you can see were added to the ComboBox in the code above.
That if/else statement updated the $TSLocation variable which I then use to force the selection of one of the dropdown options in the ComboBox. This works as well, but unfortunately it does not enable the continue button as I would like. I had a hard time looking up issues about this because its super particular and I am probably doing this incorrectly (I have very little experience with Powershell scripting). If you have any additional questions about this please let me know. Thanks!
Ok, this might illustrate your problem.
Just because you set the SelectedItem value to something, doesn't mean the SelectedIndex changes
#
Add-Type -AssemblyName System.Windows.Forms -ErrorAction Stop
#
$TSLocation = '2'
#
$form = New-Object System.Windows.Forms.Form
$form.Text = "Test"
$form.MinimumSize = '430,495'
$form.MaximumSize = '430,545'
$form.StartPosition = 'CenterScreen'
#
# Add form objects
#
$TSTypeBox = New-Object System.Windows.Forms.ComboBox
$TSTypeBox.Name = "TSType"
$TSTypeBox.Location = '116,100'
$TSTypeBox.Size = '145,20'
$TSTypeBox.add_MouseHover($ShowHelp)
$TSTypeBox.DropDownStyle = "DropDownList"
Foreach ($item in ("1","2","3","4","5")) {
$TSTypeBox.Items.Add($item) | Out-Null
}
$ComputerNameBox = New-Object System.Windows.Forms.TextBox
$ComputerNameBox.Location = '120,20'
$ComputerNameBox.Size = '120,17'
$ComputerNameBox.Text = 'test'
$OutputBox = New-Object System.Windows.Forms.TextBox
$OutputBox.Location = '120,240'
$OutputBox.Size = '120,17'
$OkButton = New-Object System.Windows.Forms.Button
$OkButton.Location = '120,200'
$OkButton.Size = '54,24'
$OkButton.Text = 'OK'
$form.controls.AddRange(#($TSTypeBox,$OkButton,$ComputerNameBox,$OutputBox))
#
# Main Script goes here
#
$handler_TSTypeBox_SelectedIndexChanged= {
$OutputBox.Text = "SelectedIndex is " + $TSTypeBox.SelectedIndex
If (($TSTypeBox.Text) -and ($ComputerNameBox.Text))
{
$OKButton.Enabled = 1
}
Else
{
$OKButton.Enabled = 0
}
}
$TSTypeBox.add_SelectedIndexChanged($handler_TSTypeBox_SelectedIndexChanged)
#
$TSTypeBox.SelectedIndex = $TSTypeBox.FindStringExact($TSLocation)
#
# Show form
$form.ShowDialog() | Out-Null
$form.Dispose()
# End
If in doubt, always best to simplify your script and add debug ,logging or output that shows what values are changing
Now that the problem is clear - this article points you in the right direction:
How do I set the selected item in a comboBox to match my string using C#?
I'm using a Datagridview inside a windows form to display some data.
The data is loaded in the background after pressing a button.
This works fine when I press the button for the first time.
But I need to be able, to do this again and again without closing the form.
No matter what I try, after pressing ok a second time, my datagridview turns into white ground with big red x across it. I would like to empty/reset everything.
Here is a cut down version of my code:
Add-Type -AssemblyName System.Windows.Forms
$sync = [Hashtable]::Synchronized(#{})
$backgroundTask = {
$task1 = [PowerShell]::Create().AddScript({
#here I would like to clear all data from a previous execution. But nothing worked.
$softwareQuery = Get-WMIObject -ComputerName localhost -Class Win32_Product | Select Name
$softwareList = New-Object System.collections.ArrayList
$softwareList.AddRange($softwareQuery)
$sync.softwareTable.DataSource = $softwareList
})
$runspace = [RunspaceFactory]::CreateRunspace()
$runspace.ApartmentState = "STA"
$runspace.ThreadOptions = "ReuseThread"
$runspace.Open()
$runspace.SessionStateProxy.SetVariable("sync", $sync)
$task1.Runspace = $runspace
$task1.BeginInvoke()
}
$mainForm = New-Object system.Windows.Forms.Form
$mainForm.Size = New-Object System.Drawing.Size(900,600)
$okButton = New-Object System.Windows.Forms.Button
$okButton.Location = New-Object System.Drawing.Point(280,5)
$okButton.Size = New-Object System.Drawing.Size(75,20)
$okButton.Text = "OK"
$okButton.Add_Click($backgroundTask) ;
$mainForm.Controls.Add($okButton)
$softwareTable = New-Object System.Windows.Forms.DataGridView -Property #{
Location=New-Object System.Drawing.Point(5,30)
Size=New-Object System.Drawing.Size(840,360)
ColumnHeadersVisible = $true
}
$mainForm.Controls.Add($softwareTable)
$sync.softwareTable = $softwareTable
$mainForm.ShowDialog()
I see two issues, the first that you need to clear the datasource, and the second is that you're trying to call the action too soon after clicking the button and opening the runspace. Put a simple start-sleep in there and that issue should be resolved. I'd recommend playing with the time that you sleep it for to get the fastest execution. The second is also an easy fix, just clear the datasource before you declare what task1 is. This worked for me.
$backgroundTask = {
$sync.softwareTable.DataSource = $null
$task1 = [PowerShell]::Create().AddScript({
#here I would like to clear all data from a previous execution. But nothing worked.
$softwareQuery = Get-WMIObject -ComputerName virinfpshd001w -Class Win32_Product | Select Name
$softwareList = New-Object System.collections.ArrayList
$softwareList.AddRange($softwareQuery)
$sync.softwareTable.DataSource = $softwareList
})
$runspace = [RunspaceFactory]::CreateRunspace()
$runspace.ApartmentState = "STA"
$runspace.ThreadOptions = "ReuseThread"
$runspace.Open()
$runspace.SessionStateProxy.SetVariable("sync", $sync)
$task1.Runspace = $runspace
$handle = $task1.BeginInvoke()
Start-Sleep -seconds 5
}
I think one of the issues might be that you are trying to update a form control from a different thread. I am not an expert but in my experience that could go wrong in some ways.
To clear the datagridview, have you tried to set the Datasource property of the datagridview to $null before assigning it a new value? That is how I usually get mine done.
I hope there i can get an answer for this quick. Im populating a list box based on radio button being selected. I want the list box to highlight the row automatically so that when i click the button the relevant action can happen based on the selection. In this case it will be a sql server instance. See extract of code for adding it to ListBox1.
ForEach ($Server in $Servers)
{
#$NL = "`r`n"
[void] $ListBox1.Items.Add($Server)
#$ListBox1.Items.selectedItem
}
Example:
$form = New-Object System.Windows.Forms.Form
$listbox = New-Object System.Windows.Forms.ListBox
$listbox.SelectionMode = "MultiSimple"
$listbox.Items.Add("item1") | Out-Null
$listbox.Items.Add("item2") | Out-Null
$listbox.Items.Add("item3") | Out-Null
$listbox.Items.Add("item4") | Out-Null
for($i = 0; $i -lt $listbox.Items.Count; $i++) {
$listbox.SetSelected($i, $true)
}
$form.Controls.Add($listbox)
$form.ShowDialog()
I want to know if it´s possible to disable Win+D Windows-Shortkey?
And the "view Desktop"-Button in the bottom right corner must be disabled, too.
Only for my Script and I´ve got the approach to catch this two processes and after completely minimizing all running programms, THAN open up the Script (only) again. So the Script is everytime viewable on the desktop.
The Script owns a Form without a Controlbox.
Suggest it like a Windows-Sidebar-Element that is allways viewable on the Desktop.
To Save the $PID in a extra LOCAL-File (Script running on each PC from a Server) is necessary, because an other Script in the background run the Script again, if a worker would kill the Task.
Here is my Script Code:
#Clear Sessions
Get-PSSession | Remove-PSSession
#Save current used Process-ID in a TextFile
$PID > C:\Users\Public\Documents\PID.txt
#Imports
[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Drawing")
[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")
#$Icon = New-Object System.Drawing.Icon ("Icon-Path here")
#Get NetworkData
$hostname = $env:computername
$IPAdress = (gwmi Win32_NetworkAdapterConfiguration | ? { $_.IPAddress -ne $null }).ipaddress[0]
#Create a Form
$myForm = New-Object System.Windows.Forms.Form
$myForm.DataBindings.DefaultDataSourceUpdateMode = 0
$myForm.Size = New-Object System.Drawing.Size(190,70)
$myForm.Text ="Nutzerdaten"
#Optic-Settings
$myForm.FormBorderStyle = "FixedDialog"
$myForm.MaximizeBox = $false
$myForm.MinimizeBox = $false
$myForm.ControlBox = $false
$myForm.StartPosition = "CenterScreen"
$myForm.ShowInTaskbar = $false #Close-Window-Button unlocked
$myForm.Opacity = 0.6 #0.1 - 1 for transparancy
#$myForm.Icon = $Icon
#Disable Win+D
$myForm.Add_KeyDown({
if( $_.KeyCode -eq "LWin" )
{
Start-Sleep -Seconds 5
[System.Windows.Forms.MessageBox]::Show( "Später wird das Fenster wieder sichtbar gemacht, wenn man Win+D gedrückt hat." )
}
})
#Disable normal closeing (only by crash Script or end PwerShell in Task-Manager)
$myForm.add_FormClosing({$_.Cancel = $true})
#Label with Hostname Data
$lblHost = New-Object System.Windows.Forms.Label
$lblHost.Size = New-Object System.Drawing.Size(180,15)
$lblHost.Top = "5"
$lblHost.Left = "5"
$lblHost.Text = "Hostname : " + $hostname #View Hostname in transparancy mode
$myForm.Controls.Add($lblHost)
#on Click open MessageBox and show closeable Dialog with the IP-Adress and Hostname)
$lblHost.Add_Click({
[System.Windows.Forms.MessageBox]::Show("Hostname = "+$hostname+" | IP-Adress = "+$IPAdress )
})
#Label with IP-Adress (v4 only)
$lblIP = New-Object System.Windows.Forms.Label
$lblIP.Size = New-Object System.Drawing.Size(180,15)
$lblIP.Top = "20"
$lblIP.Left = "5"
$lblIP.Text = "IP-Adress : " + $IPAdress #View Ip-Adress in transparancy mode
$myForm.Controls.Add($lblIP)
#on Click open MessageBox and show closeable Dialog with the IP-Adress and Hostname)
$lblIP.Add_Click({
[System.Windows.Forms.MessageBox]::Show("Hostname = "+$hostname+" | IP-Adress = "+$IPAdress )
})
#View Form/GUI
$myForm.ShowDialog()
Thank you for reading this and I hope that some got any idear how to clear this problem.
Kind regards,
M. Lengl
I'm having trouble with multiple issues with a checkedlistbox. Its content is the Windows feature name (commandline parameter to install a Windows feature via Powershell) and a description which really is its more readable name. Because I develop on Windows 7 and this command is only available on a Server platform I read the data from a XML file source. The XML file was created by the output of function BuildFeaturesFile, below. I've pasted a sample at the bottom if that is a problem.
1) I have to click an item twice to check it.
2) I have to double click an item slowly and surely to avoid the program missing the second click. Would I need to change the Windows control panel config to affect this or if I wanted (not too bothered but more curious) could I reduce the polling time to slicken the interface response?
3) This is my main issue. If I use the mouse to select an item it normally updates the count correctly however when I use the keyboard despite calling the same function I get different results; the first check is seemingly missed (doubled by the look of it reading further online to undo its action - however the event "only" fires once which causes misalignment of the displayed count to the true count. I think it maybe linked to the remark in the ItemCheck event documentation "The check state is not updated until after the ItemCheck event occurs."
http://msdn.microsoft.com/en-us/library/system.windows.forms.checkedlistbox.itemcheck(v=vs.110).aspx
The main code also updates a description text box but I removed that for brevity.
4) In function ftn_CheckAllItemsInCheckList the top remmed out line works for the first item but as I'm calling it by index seemingly the action of checking the item causes the index to change which unseats the action causing it to break.
Please can you assist - at least with questions #3 and #4 if possible?
Thanks in advance.
Shaun
function BuildForm
{
# Declare objects
$frm_BuildConfigurator = New-Object System.Windows.Forms.Form
$btn_Cancel = New-Object System.Windows.Forms.Button
$gb_CC_Features = New-Object Windows.Forms.GroupBox
$clb_CC_Features = New-Object System.Windows.Forms.CheckedListBox
$btn_CC_Features_UncheckList = New-Object System.Windows.Forms.Button
$btn_CC_Features_SelectAll = New-Object System.Windows.Forms.Button
$gb_CCF_Description = New-Object Windows.Forms.GroupBox
$tb_CCF_Description = New-Object System.Windows.Forms.TextBox
#Build the form
$frm_BuildConfigurator.Text = "Build Configurator"
$frm_BuildConfigurator.StartPosition = "CenterScreen"
$frm_BuildConfigurator.Width = 380
$frm_BuildConfigurator.Height = 200
$frm_BuildConfigurator.FormBorderStyle = "FixedSingle"
$frm_BuildConfigurator.ControlBox = $false
$frm_BuildConfigurator.Controls.Add($btn_Cancel)
#Set default button behaviour
$frm_BuildConfigurator.KeyPreview = $True
$frm_BuildConfigurator.Add_KeyDown({if ($_.KeyCode -eq "Enter") {$frm_BuildConfigurator.Close()}})
$frm_BuildConfigurator.Add_KeyDown({if ($_.KeyCode -eq "Escape") {$frm_BuildConfigurator.Close()}})
# Create the Cancel button
$btn_Cancel.Location = New-Object System.Drawing.Size(50,145)
$btn_Cancel.Size = New-Object System.Drawing.Size(55,23)
$btn_Cancel.Text = "Cancel"
$btn_Cancel.Add_Click({$frm_BuildConfigurator.Close()})
# Create the Features form elements
$frm_BuildConfigurator.Controls.Add($gb_CC_Features)
$frm_BuildConfigurator.Controls.Add($gb_CCF_Description)
# Create the Features group box
$gb_CC_Features.DataBindings.DefaultDataSourceUpdateMode = [System.Windows.Forms.DataSourceUpdateMode]::OnValidation
$gb_CC_Features.Location = New-Object System.Drawing.Point(10,6)
$gb_CC_Features.Name = "gb_CC_Features"
$gb_CC_Features.Size = New-Object System.Drawing.Size(350,132)
$gb_CC_Features.Text = "Features (0 selected)"
$gb_CC_Features.Controls.Add($clb_CC_Features)
$gb_CC_Features.Controls.Add($btn_CC_Features_SelectAll)
$gb_CC_Features.Controls.Add($btn_CC_Features_UncheckList)
$clb_CC_Features.Location = New-Object Drawing.Point 11,16
$clb_CC_Features.Size = New-Object System.Drawing.Size(220,110)
$clb_CC_Features.Add_ItemCheck({ftnUpdateFeatureSelectionCount})
$clb_CC_Features.Add_SelectedIndexChanged({ftnUpdateFeatureSelectionCount})
$clb_CC_Features.Add_Click({ftnUpdateFeatureSelectionCount})
# Populate the Features checked list box
ForEach ($FeatureItem in $script:xmlFeatures.FeatureList.Feature | Select-Object -Property Name) {
$clb_CC_Features.Items.Add($FeatureItem.Name) | Out-Null
}
# Create the Check All button
$btn_CC_Features_SelectAll.Location = New-Object System.Drawing.Point(250,20)
$btn_CC_Features_SelectAll.Size = New-Object System.Drawing.Size(80,23)
$btn_CC_Features_SelectAll.Text = "Check All"
$btn_CC_Features_SelectAll.Add_Click({ftn_CheckAllItemsInCheckList $clb_CC_Features})
$btn_CC_Features_SelectAll.TabIndex = 2
# Create the Uncheck All button
$btn_CC_Features_UncheckList.Location = New-Object System.Drawing.Point(250,50)
$btn_CC_Features_UncheckList.Size = New-Object System.Drawing.Size(80,23)
$btn_CC_Features_UncheckList.Text = "Uncheck All"
$btn_CC_Features_UncheckList.Add_Click({ftn_UncheckList $clb_CC_Features})
$btn_CC_Features_UncheckList.TabIndex = 2
ftnUpdateFeatureSelectionCount
#Show the Form
$frm_BuildConfigurator.ShowDialog() | Out-Null
}
function ftnUpdateFeatureDescription()
{
$tb_CCF_Description.Text = $script:FeatureDisplayNames[$clb_CC_Features.SelectedIndex]
}
function ftnUpdateFeatureSelectionCount ()
{
$gb_CC_Features.Text = "Features (" + $clb_CC_Features.CheckedItems.Count + " selected)"
}
function BuildFeaturesFile ()
{
# Only runs on Windows Server
$Features = Get-WindowsFeature
$xml = "<xml>"
$NewFeaturesFilePath = $ScriptDir + "\FeaturesNew.xml"
ForEach($Feature in $Features)
{
$xml += "<Feature Name='" + $Feature.Name + "' DisplayName='" + $Feature.DisplayName + "'>"
$xml += "</Feature>"
}
$xml += "</xml>"
$xml | Out-File -FilePath $NewFeaturesFilePath
}
function ReadFeaturesFile ()
{
[array] $script:FeatureNames = $null
[array] $script:FeatureDisplayNames = $null
$FileExists = Test-Path $FeaturesFilePath
if ($FileExists -eq $true) {
$script:xmlFeatures.Load($FeaturesFilePath)
$xml_Features = $script:xmlFeatures.SelectNodes("/FeatureList/Feature")
ForEach ($Feature in $xml_Features) {
[array] $script:FeatureNames += $Feature.Name
[array] $script:FeatureDisplayNames += $Feature.DisplayName
}
}
}
function ftn_UncheckList( $checkedListBoxObject )
{
ForEach($i in $checkedListBoxObject.CheckedIndices) { $checkedListBoxObject.SetItemCheckState($i, 'Unchecked') | Out-Null }
}
function ftn_CheckAllItemsInCheckList( $checkedListBoxObject )
{
#ForEach($i in $checkedListBoxObject.Items) { $checkedListBoxObject.SetItemChecked($checkedListBoxObject.Items.IndexOf($i), $true) }
For($index =0; $index -lt $checkedListBoxItem.Items.Count; $index++){ if($checkedListBoxItem.GetItemChecked($index)) { $checkedListBoxItem.SetItemChecked($i, $true); }}
}
Set-ExecutionPolicy -ExecutionPolicy Unrestricted -Force
[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Drawing")
[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")
$script:xmlFeatures = $null; $script:xmlFeatures = New-Object -TypeName XML
$ScriptPath = $MyInvocation.MyCommand.Path
$ScriptDir = Split-Path -parent $ScriptPath
$FeaturesFilePath = $ScriptDir + "\Features.xml"
$BuildScriptStr = $MyInvocation.MyCommand.Definition #Full path - for script name only use $MyInvocation.MyCommand.Name
$noSelectedFeatures = 0
ReadFeaturesFile
BuildForm
# End of script
File: Features.xml
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<FeatureList>
<Feature Name="AD-Certificate" DisplayName="Active Directory Certificate Services"/>
<Feature Name="ADCS-Cert-Authority" DisplayName="Certification Authority"/>
<Feature Name="ADCS-Web-Enrollment" DisplayName="Certification Authority Web Enrollment"/>
</FeatureList>
There are a lot of questions :
1)-2) If you want to click once to check a box you have to set the CheckOnClick property on the CheckedListBox. In you case a line can be checked only once it's selected.
$clb_CC_Features.Size = New-Object System.Drawing.Size(220,110)
$clb_CC_Features.CheckOnClick = $true
$clb_CC_Features.Add_ItemCheck({})
3) The message ItemCheck append before the line is really checked so in the function you call on this even you have look if the line is going to be cheched or unchecked.
I change
$clb_CC_Features.Add_ItemCheck({ftnChecked})
#$clb_CC_Features.Add_SelectedIndexChanged({ftnUpdateFeatureSelectionCount})
#$clb_CC_Features.Add_Click({})
...
$_ represent the value of the event.
function ftnChecked ()
{
if ($_.NewValue -eq 'checked')
{
$gb_CC_Features.Text = "Features (" + $($clb_CC_Features.CheckedItems.Count + 1) + " selected)"
}
else
{
$gb_CC_Features.Text = "Features (" + $($clb_CC_Features.CheckedItems.Count -1) + " selected)"
}
}
4) You can try the following :
function ftn_CheckAllItemsInCheckList #( $checkedListBoxObject )
{
For($index =0; $index -lt $clb_CC_Features.Items.Count; $index++){ $clb_CC_Features.SetItemChecked($index, $true)}
}