Dynamic Drop Down List With Powershell and Xml

I created this utility as part of a set of tools for a Production Operation Environment, where something as simple and innocuous as File-Transfers needed to have structure. Since I didn’t have much experience with Powershell, I thought this would be a good little project to take on in order to get my feet wet.

The Challenge

You might be wondering, “Why do File-Transfers have to have a GUI?”. This function is at the tail end of a process-chain and acts as an Audit Checkpoint where an end-user must validate the files for Accuracy and Completeness prior to sending the files for processing. These files contain configuration information that is vital to our day-to-day process. Any files that are not accurate or complete, could result in delays and thousands upon thousands of dollars in lost revenue and operational recovery.

Here is what I knew:

  1. Program has to have a GUI for the end-users to easily navigate and operate
  2. File Structure can vary, as the specifications have not been finalized
  3. Utility has to be delivered, deployed and tested within a week

The XML Configuration

Prior to creating any GUI, I had to create an XML Config that is easily configurable and used to contain the values for the Drop-Down List.

<?xml version="1.0" encoding="UTF-8"?>
<config>
	<type tName="Pictures">
		<group gName="Pictures 01" aDate="">
			<description>Insert Description</description>
			<directory fLocation="C:\GitHub\Powershell\BulkFileTransfer\Pictures01\"></directory>
		</group>
		<group gName="Pictures 02" aDate="">
			<description>Insert Description</description>
			<directory fLocation="C:\GitHub\Powershell\BulkFileTransfer\Pictures02\"></directory>
		</group>		
		<group gName="Pictures 03" aDate="">
			<description>Insert Description</description>
			<directory fLocation="C:\GitHub\Powershell\BulkFileTransfer\Pictures03\"></directory>
		</group>			
	</type>
	<type tName="Files">
		<group gName="Files 01" aDate="">
			<description>Insert Description</description>
			<directory fLocation="C:\GitHub\Powershell\BulkFileTransfer\Files01\"></directory>
		</group>
		<group gName="Files 02" aDate="">
			<description>Insert Description</description>
			<directory fLocation="C:\GitHub\Powershell\BulkFileTransfer\Files02\"></directory>
		</group>		
		<group gName="Files 03" aDate="">
			<description>Insert Description</description>
			<directory fLocation="C:\GitHub\Powershell\BulkFileTransfer\Files03\"></directory>
		</group>			
	</type>
</config>

The GUI

I did not have any frame of reference for creating a GUI in Powershell, so I googled powershell gui and I ended up using PoshGUI to assist me in generating the form. I did not end up using the code produced by the tool, but I used the object types I needed as reference.

  • System.Drawing.Point
  • System.Windows.Forms.Form
  • System.Windows.Forms.Label
  • System.Windows.Forms.ComboBox
  • System.Windows.Forms.ListBox
  • System.Windows.Forms.Button
  • [System.Windows.Forms.ComboBoxStyle]::DropDownList
#Requires -Version 3.0

Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName PresentationFramework

[System.Windows.Forms.Application]::EnableVisualStyles()
[Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms") | Out-Null

#region Form Configuration
$fileUploadForm                      = New-Object system.Windows.Forms.Form
$fileUploadForm.ClientSize           = '390,330'
$fileUploadForm.Text                 = "Bulk File Transfer Utility"
$fileUploadForm.TopMost              = $false
$fileUploadForm.MaximizeBox          = $false
$fileUploadForm.MinimizeBox          = $false
$fileUploadForm.FormBorderStyle      = 'Fixed3D'
$fileUploadForm.MaximizeBox          = $false
$fileUploadForm.AutoSize             = $true

$loggedinUser                        = New-Object System.Windows.Forms.Label
$loggedinUser.Text                   = ("Hello ")+$env:USERNAME.ToUpper()+("!")
$loggedinUser.AutoSize               = $true
$loggedinUser.Width                  = 100
$loggedinUser.Height                 = 35
$loggedinUser.Location               = New-Object System.Drawing.Point(22,25)
$loggedinUser.Font                   = 'Microsoft Sans Serif,15'

$typeSelectLabel                     = New-Object system.Windows.Forms.Label
$typeSelectLabel.Text                = "Step 1: Choose Folder"
$typeSelectLabel.AutoSize            = $true
$typeSelectLabel.Width               = 25
$typeSelectLabel.Height              = 10
$typeSelectLabel.Location            = New-Object System.Drawing.Point(22,90)
$typeSelectLabel.Font                = 'Microsoft Sans Serif,10'

$typeSelectList                      = New-Object system.Windows.Forms.ComboBox
$typeSelectList.Width                = 165
$typeSelectList.Height               = 20
$typeSelectList.Location               = New-Object System.Drawing.Point(22,115)
$typeSelectList.Font                 = 'Microsoft Sans Serif,8'
$typeSelectList.DropDownStyle        = [System.Windows.Forms.ComboBoxStyle]::DropDownList
$typeSelectList.Enabled              = $true

$groupSelectLabel                    = New-Object system.Windows.Forms.Label
$groupSelectLabel.Text               = "Step 2: Select Folder"
$groupSelectLabel.AutoSize           = $true
$groupSelectLabel.Width              = 25
$groupSelectLabel.Height             = 10
$groupSelectLabel.Location           = New-Object System.Drawing.Point(205,90)
$groupSelectLabel.Font               = 'Microsoft Sans Serif,10'

$groupSelectList                     = New-Object system.Windows.Forms.ComboBox
$groupSelectList.Width               = 165
$groupSelectList.Height              = 20
$groupSelectList.Location            = New-Object System.Drawing.Point(205,115)
$groupSelectList.Font                = 'Microsoft Sans Serif,8'
$groupSelectList.DropDownStyle       = [System.Windows.Forms.ComboBoxStyle]::DropDownList

$filesinGroupDirectory               = New-Object System.Windows.Forms.ListBox
$filesinGroupDirectory.Width         = 347
$filesinGroupDirectory.Height        = 135
$filesinGroupDirectory.Location      = New-Object System.Drawing.Point(22,150)
$filesinGroupDirectory.Font          = 'Microsoft Sans Serif,10'
$filesinGroupDirectory.Enabled       = $true
$filesinGroupDirectory.Sorted        = $true
                                   
$sendButton                          = New-Object System.Windows.Forms.Button
$sendButton.Text                     = "Step 3: Send Files!"
$sendButton.Width                    = 165
$sendButton.Height                   = 30
$sendButton.Enabled                  = $false
$sendButton.Location                 = New-Object System.Drawing.Point(205,290)
$sendButton.Font                     = 'Microsoft Sans Serif,10'

$fileUploadForm.controls.AddRange(@($groupSelectLabel,$groupSelectList,
                                    $typeSelectList, $typeSelectLabel, $filesinGroupDirectory,
                                    $sendButton, $loggedinUser))

Functions and Handlers and Events

Functions

Next, I created a function that will store config.xml values.

Function Refresh-Product-Config() {
    $global:xml = [xml](Get-Content -Path "C:\GitHub\Powershell\BulkFileTransfer\config.xml")
}

Handlers

The following reads $global:xml on form load and gets all the attribute values tname of the element type and populates the first Drop-Down List typeSelectList.

$formLoad_handler = {
    Refresh-Product-Config
    $groups = $global:xml.SelectNodes("/config/type/@tName")
    ForEach ($item in $groups) {
        $typeSelectList.Items.Add($item.Value)
   }
}

Next, I created a handler to first clear all values in the Group Drop-Down lists and the Files List Box, and then used the selected text value of the Type Drop-Down list as an attribute tName parameter to find all the associated groups gName to populate the Group Drop-Down List.

$typeSelect_handler = {
    Refresh-Product-Config
    $groupSelectList.Items.Clear()
    $filesinGroupDirectory.Items.Clear()
    $sendButton.Enabled = $false
    [string]$global:typeSelectName = $typeSelectList.Text

    $groups = $global:xml.SelectNodes("/config/type[@tName='$global:typeSelectName']/group/@gName")

    foreach ($item in $groups) {
        $groupName = $item.Value
        $groupSelectList.Items.Add($item.Value)
    }
 
}

I then used the selected text value of the both the Type and Group Drop-Down lists as attribute parameters to get the directory attribute fLocation. That would give me a list of files $files = Get-ChildItem $global:fLocation.Value -File. If files were found, I populated the filesinGroupDirectory List and enable $sendButton. If no files were found, I showed a System.Windows.Forms.MessageBox alerting the end-user that there are no files in the selected directory.

$groupSelect_handler = {
    $filesinGroupDirectory.Items.Clear()
    [string]$global:groupSelectName = $groupSelectList.Text
    $global:fLocation = $global:xml.SelectNodes("/config/type[@tName='$global:typeSelectName']`
    /group[@gName='$global:groupSelectName']/directory/@fLocation")
    $files = Get-ChildItem $global:fLocation.Value -File
    if (($files).Count -ne 0) {
        $sendButton.Enabled = $true
        ForEach ($file in $files) {
            $filesinGroupDirectory.Items.Add($file)
        }
    }
    else {
        [System.Windows.Forms.MessageBox]::Show("No Files in Directory", "ALERT", 
        [System.Windows.Forms.MessageBoxButtons]::Ok, [System.Windows.Forms.MessageBoxIcon]::Error)
        $sendButton.Enabled = $false
    }
}

Events

Lastly, the remaining code is to associate the handlers to their respective object and event.

$fileUploadForm.Add_Shown($formLoad_handler)
$typeSelectList.Add_SelectedIndexChanged($typeSelect_handler)
$groupSelectList.Add_SelectedIndexChanged($groupSelect_handler)

Conclusion

Creating the tool was relatively painless and I was happy with the way it turned out. The tool has minimal functionality, but has had a large impact with the employees that use it. They have gained more confidence in their new workflow with this tool. It has since been deployed into several workstations on our Production/Operation floor.

Avatar
Sherwin Rubio
Business Intelligence Architect

My research interests include Business Intelligence and Data Supply Chain Architecture

Previous