none
Powershell Windows Forms ListBox does not refresh properly after item deletion RRS feed

  • Question

  • Hello,

    I'm learning to create Windows forms scripts. I have created a script that displays an AD users groups, so an admin can multi select and remove groups from a list box. Sort of an out boarding tool. I can not seem to get the ListBox to repopulate properly after Item(s) are deleted.

    I am using VS Code to create and debug so that part is cool. I've been able to determine that the selected group items are actually getting removed from the user, but they re-appear in the view. If I click on it/them a second time then they are removed from the ListBox properly.

    I've tried using Start-Sleep to slow down execution, but that doesn't help( typical with most languages). I'm sure I'm missing a step redrawing the List Box.

    My Code:

    ################################################
    # Staff GUI for Outboarding either terminated
    # or reassigned employees. This removes their 
    # currently assigned groups.
    # Mike Murphy 10-22-2019
    ################################################
    
    # Active directory Module
    import-module ActiveDirectory
    
    #GUI Framework
    Add-Type -assembly System.Windows.Forms
    Add-Type -AssemblyName System.Drawing
    
    $mainform = New-Object System.Windows.Forms.Form
    $mainform.Text ='Employee Group Removal'
    $mainform.Width = 650
    $mainform.Height = 400
    $mainform.AutoSize = $true
    $mainform.BackColor = "lightblue"
    
    #Font
    $font = New-Object System.Drawing.Font("Segoe UI",9,[System.Drawing.FontStyle]::Bold)
    
    #Header Label
    $headerlabel = New-Object System.Windows.Forms.Label
    $headerlabel.Text = "This will remove all AD security groups from the designated 4J User, except Domain User and staff."
    $headerlabel.Location = New-Object System.Drawing.Point(5,10)
    $headerlabel.AutoSize = $true
    $headerlabel.Font = $font
    $mainform.Controls.Add($headerlabel)
    
    #Header Label
    $accountlabel = New-Object System.Windows.Forms.Label
    $accountlabel.Text = "Enter the 4J account name."
    $accountlabel.Location = New-Object System.Drawing.Point(5,37)
    $accountlabel.AutoSize = $true
    $accountlabel.Font = $font
    $mainform.Controls.Add($accountlabel)
    
    #Groups Label
    $groupsLabel = New-Object System.Windows.Forms.Label
    $groupsLabel.Location = New-Object System.Drawing.Point(5,70)
    $groupsLabel.AutoSize = $true
    $groupslabel.Text = "Select the groups to remove."
    $groupslabel.Font = $font
    $mainform.Controls.Add($groupsLabel)
    
    #Account Search Label
    $accountSearchLabel = New-Object System.Windows.Forms.Label
    $accountSearchLabel.Location = New-Object System.Drawing.Point(318,37)
    $accountSearchLabel.AutoSize = $true
    $accountsearchlabel.Font = $font
    $mainform.Controls.Add($accountSearchLabel)
    
    #Account text field 
    $textBox = New-Object System.Windows.Forms.TextBox
    $textBox.Location = New-Object System.Drawing.Point(168,35)
    $textBox.Size = New-Object System.Drawing.Size(140,20)
    $mainform.Controls.Add($textBox)
    
    # Group List Box
    $listBox = New-Object System.Windows.Forms.ListBox
    $listBox.Location = New-Object System.Drawing.Point(5,95)
    $listBox.Size = New-Object System.Drawing.Size(180,20)
    $listBox.SelectionMode = 'MultiExtended'
    
    $listBox.Height = 200
    $mainform.Controls.Add($listBox)
    $mainform.Topmost = $true
    
    # Validation Button
    $vbutton = New-Object System.Windows.Forms.Button
    $vbutton.Location = New-Object System.Drawing.Point(205,95)
    $vbutton.Size = New-Object System.Drawing.Size(140,20)
    $vbutton.Text = "Check"
    $mainform.Controls.Add($vbutton)
    
    #Remove Groups Button
    $rbutton = New-Object System.Windows.Forms.Button
    $rbutton.Location = New-Object System.Drawing.Point(205,120)
    $rbutton.Size = New-Object System.Drawing.Size(140,20)
    $rbutton.Text = "Remove Groups"
    $mainform.Controls.Add($rbutton)
    
    #Vars
    $global:User = $null
    
    #Event listener for validation button
    $vbutton.Add_Click({
        validate
    })
    
    
    #Event listener for remove button
    $rButton.Add_Click({
        removeGroups
    })
    
    
    function validate{
    
        $name = $textbox.Text
    
        if($name){
    
            #Get User Object
            $global:User = Get-ADUser -Filter {SamAccountName -eq $textbox.Text}
    
            if($null -ne $global:User){
            
                $accountSearchLabel.ForeColor = "green"
                $accountSearchLabel.Text = "$name is Valid."
                
                #Display Groups Function
                displayGroups
    
            }else{
                
                  #Empty the list box and clear Vars
                  $listBox.Items.Clear()
                  $accountSearchLabel.Text = ""
                  $accountSearchLabel.Text = "User account $name not found! Please try again"
                  $accountSearchLabel.ForeColor = "red"            
            }
    
        }
    
    }
    
    #Remove the selected groups
    function removeGroups{
    
        if($listBox.Items.Count -gt 0){
    
            $deletions = $listBox.SelectedItems
    
            if($deletions){
    
                foreach($name in $deletions){
    
                    #remove user from group
                    Remove-ADGroupMember -Identity $name -Members $global:User.Name -Confirm:$false 
                }
    
            }
    
            #re-display the remaining groups in the listbox
            displayGroups
    
        }
    
    }
    
    # display the list box
    function displayGroups{
    
        $listBox.Items.Clear()
    
        $groups = Get-ADPrincipalGroupMembership -identity $User 
    
         foreach($group in $groups){
            
            if(($group.Name -ne 'staff')-and($group.Name -ne 'Domain Users')){
    
                [void] $listBox.Items.Add($group.Name)
                
            }
    
        }
    
    }
    
    
    
    #Display the form
    $mainform.ShowDialog()

    when debugging, if I put breakpoints around the $groups and $listbox vars in the display function, It seems to work fine. It acts like some sort of a thread or speed issue.

    Thanks in advance for the assistance.

     




    • Edited by mmurphy58 Thursday, October 24, 2019 11:43 PM
    Thursday, October 24, 2019 11:40 PM

Answers

  • I had some time so I cleaned up your code so you could see the issues and how to solve them with correct use of forms and forms code.  I also want to save this thread to post to others who are trying to use forms with little coding experience.

    I converted this using an advanced editor so it was pretty fast but is not complete.  There is no real error management which would be needed.

    ################################################
    # Staff GUI for Outboarding either terminated
    # or reassigned employees. This removes their 
    # currently assigned groups.
    # Mike Murphy 10-22-2019
    ################################################
    
    function Show-OutboardGroups{
    	# Modules and assemblies
    	import-module ActiveDirectory
    	Add-Type -assembly System.Windows.Forms
    
    	$mainform = New-Object System.Windows.Forms.Form
    	$mainform.Text ='Employee Group Removal'
    	$mainform.Size = '650,450'
    	$mainform.StartPosition = 'CenterScreen'
    	$mainform.BackColor = 'lightblue'
    	$mainform.Topmost = $true
    
    	$font = [System.Drawing.Font]::New('Segoe UI',9,[System.Drawing.FontStyle]::Bold)
    
    	#Header Label
    	$headerlabel = New-Object System.Windows.Forms.Label
    	$headerlabel.Text = "This will remove all AD security groups from the designated 4J User`n,except Domain User and staff."
    	$headerlabel.Location = '5,15'
    	$headerlabel.AutoSize = $true
    	$headerlabel.Font = $font
    	$mainform.Controls.Add($headerlabel)
    
    	#Header Label
    	$accountlabel = New-Object System.Windows.Forms.Label
    	$accountlabel.Text = 'Enter the 4J account name.'
    	$accountlabel.Location = '5,55'
    	$accountlabel.TextAlign = 'MiddleLeft'
    	$accountlabel.Size = '200,20'
    	$accountlabel.Font = $font
    	$mainform.Controls.Add($accountlabel)
    
    	#Groups Label
    	$groupsLabel = New-Object System.Windows.Forms.Label
    	$groupsLabel.Location = '5,80'
    	$groupsLabel.AutoSize = $true
    	$groupslabel.Text = 'Select the groups to remove.'
    	$groupslabel.Font = $font
    	$mainform.Controls.Add($groupsLabel)
    
    	#Account Search Label
    	$accountSearchLabel = New-Object System.Windows.Forms.Label
    	$accountSearchLabel.Location = '318,42'
    	$accountSearchLabel.AutoSize = $true
    	$accountsearchlabel.Font = $font
    	#$mainform.Controls.Add($accountSearchLabel)
    
    	#Account text field 
    	$textBox = New-Object System.Windows.Forms.TextBox
    	$textBox.Location = '205,55'
    	$textBox.Size = '180,20'
    	$mainform.Controls.Add($textBox)
    
    	# Group List Box
    	$listBox = New-Object System.Windows.Forms.ListBox
    	$listBox.Location = '5,105'
    	$listBox.Size = '200,300'
    	$listBox.SelectionMode = 'MultiExtended'
    	$mainform.Controls.Add($listBox)
    
    	# Validation Button
    	$vbutton = New-Object System.Windows.Forms.Button
    	$vbutton.Location = '400,55'
    	$vbutton.Size = '140,20'
    	$vbutton.Text = 'Check'
    	$mainform.Controls.Add($vbutton)
    	$vbutton.Add_Click({
    	    if($textbox.Text){
    	        if(Get-ADUser -Filter "SamAccountName -eq '$($textbox.Text)'"){
    	            $this.ForeColor = 'green'
    			    $listBox.Items.Clear()
    			    $groups = Get-ADPrincipalGroupMembership -identity $textbox.Text | where{$group.Name -notmatch '^staff$|^Domain Users$'}
    			    [void]$listBox.Items.AddRange($groups.SamAccountName)
    	        }else{
    	            $listBox.Items.Clear()
    				$this.ForeColor = 'Black'
    	            [void][System.Windows.Forms.MessageBox]::Show("User account $name not found! Please try again",'Not Found!', 'OK', 'Warning')
    	        }
    	    }
    	})
    
    
    	#Remove Groups Button
    	$rbutton = New-Object System.Windows.Forms.Button
    	$rbutton.Location = '205,105'
    	$rbutton.Size = '140,20'
    	$rbutton.Text = 'Remove Groups'
    	$mainform.Controls.Add($rbutton)
    	$rButton.Add_Click({
    		$listBox.SelectedItems |
    			ForEach-Object{
    				Remove-ADGroupMember -Identity $_ -Members $textbox.Text -Confirm:$false 
    	        }
    		    $listBox.Items.Clear()
    		    $groups = Get-ADPrincipalGroupMembership -identity $textbox.Text | where{$group.Name -notmatch '^staff$|^Domain Users$'}
    			[void]$listBox.Items.AddRange($groups.SamAccountName)
    	})
    
    	$mainform.ShowDialog()
    }
    
    Show-OutboardGroups
    
    
    You will see that this works correctly to remove a user form groups.


    \_(ツ)_/

    • Marked as answer by mmurphy58 Friday, October 25, 2019 11:16 PM
    Friday, October 25, 2019 7:57 PM

All replies

  • Remember that, when in an event, the screen will not get updated for many operations. This is because no other code can not normally be executed while stopped in an event. This has implications when running under a debugger as the debugger will also alter this behavior and will likely stop all other code execution. All of a form's code outside of the PS script runs under the debugger.


    \_(ツ)_/

    Thursday, October 24, 2019 11:51 PM
  • Thank You for the reply , but I don't know what you're getting at. Could you elaborate a bit further?

    Are you suggesting refreshing the form in each event(I assume your talking about listeners when you mention events)



    • Edited by mmurphy58 Friday, October 25, 2019 2:16 AM
    Friday, October 25, 2019 2:14 AM
  • I really can't because I have no idea what you are trying to debug. The code appears to be something you copied from multiple places and changed to get the display you wanted but without much understanding of what forms are or how forms actually work.

    I also see a few mistakes in usage which would fail even outside of a form.  In particular using "Name" to retrieve a group. "Name" cannot be used as an Identity in AD.  So you will need to spend some more time learning AD.

    I recommend making the action code work independently of a form until you see how it is intended to be used.

    Also you are creat9ing functions to call from events.  That is almost always counter-productive in a form.  Forms code should be contained in the even and should be as short as possible.

    I suspect that the real issue that you are experiencing is due to the use of "Name" as it may work on some groups and not on others.

    Some hints on how to load a ListBox:

    $groups = Get-ADPrincipalGroupMembership -identity $textbox.Text | where{$group.Name -notmatch '^staff$|^Domain Users$'}
    [void]$listBox.Items.AddRange($groups.SamAccountName)
    

    Avoid copying values from forms control into local variables.  Avoid using "global" variables when they are not needed.  YOu are testing a name for exisitence.  There is no need to save the object.

    if($textbox.Text){
         if(Get-ADUser -Filter "SamAccountName -eq '$($textbox.Text)'"){
    


    \_(ツ)_/

    Friday, October 25, 2019 2:25 AM
  • Friday, October 25, 2019 2:27 AM
  • I appreciate the tips. Ill post back later
    Friday, October 25, 2019 7:16 PM
  • I had some time so I cleaned up your code so you could see the issues and how to solve them with correct use of forms and forms code.  I also want to save this thread to post to others who are trying to use forms with little coding experience.

    I converted this using an advanced editor so it was pretty fast but is not complete.  There is no real error management which would be needed.

    ################################################
    # Staff GUI for Outboarding either terminated
    # or reassigned employees. This removes their 
    # currently assigned groups.
    # Mike Murphy 10-22-2019
    ################################################
    
    function Show-OutboardGroups{
    	# Modules and assemblies
    	import-module ActiveDirectory
    	Add-Type -assembly System.Windows.Forms
    
    	$mainform = New-Object System.Windows.Forms.Form
    	$mainform.Text ='Employee Group Removal'
    	$mainform.Size = '650,450'
    	$mainform.StartPosition = 'CenterScreen'
    	$mainform.BackColor = 'lightblue'
    	$mainform.Topmost = $true
    
    	$font = [System.Drawing.Font]::New('Segoe UI',9,[System.Drawing.FontStyle]::Bold)
    
    	#Header Label
    	$headerlabel = New-Object System.Windows.Forms.Label
    	$headerlabel.Text = "This will remove all AD security groups from the designated 4J User`n,except Domain User and staff."
    	$headerlabel.Location = '5,15'
    	$headerlabel.AutoSize = $true
    	$headerlabel.Font = $font
    	$mainform.Controls.Add($headerlabel)
    
    	#Header Label
    	$accountlabel = New-Object System.Windows.Forms.Label
    	$accountlabel.Text = 'Enter the 4J account name.'
    	$accountlabel.Location = '5,55'
    	$accountlabel.TextAlign = 'MiddleLeft'
    	$accountlabel.Size = '200,20'
    	$accountlabel.Font = $font
    	$mainform.Controls.Add($accountlabel)
    
    	#Groups Label
    	$groupsLabel = New-Object System.Windows.Forms.Label
    	$groupsLabel.Location = '5,80'
    	$groupsLabel.AutoSize = $true
    	$groupslabel.Text = 'Select the groups to remove.'
    	$groupslabel.Font = $font
    	$mainform.Controls.Add($groupsLabel)
    
    	#Account Search Label
    	$accountSearchLabel = New-Object System.Windows.Forms.Label
    	$accountSearchLabel.Location = '318,42'
    	$accountSearchLabel.AutoSize = $true
    	$accountsearchlabel.Font = $font
    	#$mainform.Controls.Add($accountSearchLabel)
    
    	#Account text field 
    	$textBox = New-Object System.Windows.Forms.TextBox
    	$textBox.Location = '205,55'
    	$textBox.Size = '180,20'
    	$mainform.Controls.Add($textBox)
    
    	# Group List Box
    	$listBox = New-Object System.Windows.Forms.ListBox
    	$listBox.Location = '5,105'
    	$listBox.Size = '200,300'
    	$listBox.SelectionMode = 'MultiExtended'
    	$mainform.Controls.Add($listBox)
    
    	# Validation Button
    	$vbutton = New-Object System.Windows.Forms.Button
    	$vbutton.Location = '400,55'
    	$vbutton.Size = '140,20'
    	$vbutton.Text = 'Check'
    	$mainform.Controls.Add($vbutton)
    	$vbutton.Add_Click({
    	    if($textbox.Text){
    	        if(Get-ADUser -Filter "SamAccountName -eq '$($textbox.Text)'"){
    	            $this.ForeColor = 'green'
    			    $listBox.Items.Clear()
    			    $groups = Get-ADPrincipalGroupMembership -identity $textbox.Text | where{$group.Name -notmatch '^staff$|^Domain Users$'}
    			    [void]$listBox.Items.AddRange($groups.SamAccountName)
    	        }else{
    	            $listBox.Items.Clear()
    				$this.ForeColor = 'Black'
    	            [void][System.Windows.Forms.MessageBox]::Show("User account $name not found! Please try again",'Not Found!', 'OK', 'Warning')
    	        }
    	    }
    	})
    
    
    	#Remove Groups Button
    	$rbutton = New-Object System.Windows.Forms.Button
    	$rbutton.Location = '205,105'
    	$rbutton.Size = '140,20'
    	$rbutton.Text = 'Remove Groups'
    	$mainform.Controls.Add($rbutton)
    	$rButton.Add_Click({
    		$listBox.SelectedItems |
    			ForEach-Object{
    				Remove-ADGroupMember -Identity $_ -Members $textbox.Text -Confirm:$false 
    	        }
    		    $listBox.Items.Clear()
    		    $groups = Get-ADPrincipalGroupMembership -identity $textbox.Text | where{$group.Name -notmatch '^staff$|^Domain Users$'}
    			[void]$listBox.Items.AddRange($groups.SamAccountName)
    	})
    
    	$mainform.ShowDialog()
    }
    
    Show-OutboardGroups
    
    
    You will see that this works correctly to remove a user form groups.


    \_(ツ)_/

    • Marked as answer by mmurphy58 Friday, October 25, 2019 11:16 PM
    Friday, October 25, 2019 7:57 PM
  • I really appreciate that you took the time to show me better coding practices with PS-GUI, however, your code does the same thing mine did; I have to select the group a second time and press remove to remove it from the List Box even though it successfully removes from AD the first time.

    When I originally stated, "when debugging, if I put breakpoints around the $groups and $listbox vars in the display function, It seems to work fine", I simply meant that having break points in the code halted the execution enough that it worked correctly, but when I tried to use Start-Sleep in place of break points it did not make a difference. I understand that for most programming languages, sleeping a thread is not the correct technique to slow execution.

    Something is still a miss in the code.

    Thanks

    Friday, October 25, 2019 9:40 PM
  • It works correctly for me. You must catch the errors and you must only use the SamAccountName of the groups. The Name property will not access the group.

    My code with no changes works.  I suspect you changed something that got you back to your original mistake.

    What did you change?


    \_(ツ)_/


    • Edited by jrv Friday, October 25, 2019 10:09 PM
    Friday, October 25, 2019 10:08 PM
  • I pasted your code right into the editor. I didn't change anything. The only issue I had with my code was the ListBox not updating its contents after a group deletion. I'm getting that same issue with your code.

    I'm not using my Domain Admin account. I put my daily Domain User Account in the Operators Group, which allows for editing group memberships. Maybe that's the issue.

    I'll try logging in with my Admin account and see if I get different results. I seem to remember it working(my original code) when I tried it that way. I have not seen any permissions issues though.


    • Edited by mmurphy58 Friday, October 25, 2019 10:27 PM
    Friday, October 25, 2019 10:22 PM
  • What editor? Just paste it in a CLI and run.

    I just copied my code above and opened a new CLI console and pasted it. It works exactly as needed.  If you have issues then something else must be wrong,

    PowerShell 5.1


    \_(ツ)_/


    • Edited by jrv Friday, October 25, 2019 10:38 PM
    Friday, October 25, 2019 10:37 PM
  • Yeah, I agree. There maybe something else going on. I made a cli version of this before I tried the GUI version and it works fine.

    Thanks for all the help

    Friday, October 25, 2019 11:16 PM