Tuesday, September 30, 2008

Tutorial: Using .NET GUI Widgets in PowerShell

One of the great unsung features of PowerShell (which forms the foundation of the Exchange Management Shell) is its ability to expose the full range of functionality offered by the .NET framework, and, more specifically, the GUI widgets provided by that framework.  I was blissfully unaware of this until encountering a sample script on "Glen's Exchange Dev Blog".  (That blog, by the way, is an excellent resource, and I highly recommend it. Entirely different Glen, lest there be any confusion. He is far more knowledgable about this stuff than I.)
In the script contained in that blog entry (upon which the following script is largely based) I discovered the wonderful world of rendering GUI elements from within PowerShell scripts. I was able to learn quite a bit about the subject by dissecting that script and a few other examples found around the web, but I was unable to find anything in the way of a tutorial on the topic.  Furthermore, although the .NET frameworks are well-documented on the MSDN website, the proper syntax for accessing those objects via PowerShell is not provided.
What else could I do other than write my own tutorial?
This screenshot illustrates what the final product will look like.

No fancy IDE such as Visual Studio is required.  I do all of this in Notepad on a machine with the Exchange Management Shell installed. (The example script is Exchange-specific, thus the requirement for EMS; but the GUI widgets can also be utilized in a pure PowerShell environment.)
Let us begin by opening a blank document in Notepad and typing in the following commands for loading in the .NET assemblies for the drawing tools and GUI widgets (watch for line wrapping introduced by the browser):
[System.Reflection.Assembly]::LoadWithPartialName("System.Drawing")
[System.Reflection.Assembly]::LoadWithPartialName("System.windows.forms")
The "System.Drawing" namespace exposes DGI+ graphics routines (see http://msdn.microsoft.com/en-us/library/system.drawing.aspx ), where the "System.windows.forms" namespace provides standard Windows interface features (see http://msdn.microsoft.com/en-us/library/system.windows.forms.aspx ).
For the sake of readability, as well as to ensure that commands are issued in the proper order, I divide the remaining portion of the script into three sections, delimited and labeled by comment lines, corresponding to function definitions, widget definitions, and the main body of code.
Let us define the window into which we will be drawing our interface:
# widget definitions

$myWindow = new-object System.Windows.Forms.form
Now, we set its parameters, specifically its name, size, and so forth, then tell it to display itself:
# main program body

$myWindow.Text = "Exchange 2007 GUI Demo"
$myWindow.size = new-object System.Drawing.Size(1000,600)
$myWindow.autoscroll = $true
$myWindow.topmost = $true
$myWindow.Add_Shown({$myWindow.Activate()})
$myWindow.ShowDialog()
Now we can save this script and run it from the shell.  Remember to save the script with a ".ps1" suffix. To run the saved script, open the Exchange Management Shell, use 'cd' commands to navigate to the directory where you've save it, then invoke the script by name.  Be sure to prefix it with ".\" to tell the shell to look for the script in the current working directory rather than in predefined system paths. It isn't terribly exciting or functional. Essentially, we have here a classic "hello, world" script which doesn't even bother to say "hello, world." After all, we are only drawing an empty window onto the screen, but it is a workable skeleton from which to hang the rest of our script.
Notice that our window stays on top, regardless of what other window might have focus.  This is because we've set the $myWindow.topmost attribute to true. I've only included this to demonstrate one of the attributes available for window objects.  If you like, delete the relevant line, or comment it out by prefixing it with a "#" character. Then save and run the script again to see the change in window behavior. (You remembered to kill off the previous window, right?)
You probably noticed that, as always, our little PowerShell script is a little slow in getting started.  This is unfortunately quite common, and endemic to the design of PowerShell since the CLR is bloody slow at loading up .NET libraries.  Take a look here for a tip on speeding up the performance of PowerShell, especially the startup of Exchange Management Shell and Exchange Management Console.
Let's put in a way of specifying the server from which we will be pulling our data. We will create a drop-down menu populated with the names of all of the servers in our Exchange organization, but first let's create a label object for this menu. Right after the initial definition of $myWindow, but before the main section where we set its attributes and tell it to display itself, add the following text:
# Add Server Drop Label
$ServerNamelabelBox = new-object System.Windows.Forms.Label
$ServerNamelabelBox.Location = new-object System.Drawing.Size(10,30)
$ServerNamelabelBox.size = new-object System.Drawing.Size(80,20)
$ServerNamelabelBox.Text = "Server Name"
$myWindow.Controls.Add($ServerNamelabelBox)
We have to define this and any other widget and add it to $myWindow before we tell $myWindow to display itself, otherwise the widget won't show up. Here, we've created the label object, its location, size, and text contents, then added it to the list of controls in the $myWindow object.
Next, we move on to the drop-down menu itself. Right after the code you've just added (again, this has to be before you actually draw the window), add the following:
# Add Server Drop Down Widget
$ServerNameDrop = new-object System.Windows.Forms.ComboBox
$ServerNameDrop.Location = new-object System.Drawing.Size(90,30)
$ServerNameDrop.Size = new-object System.Drawing.Size(100,30)
get-mailboxserver | ForEach-Object{$ServerNameDrop.Items.Add($_.Name)}
$ServerNameDrop.Add_SelectedValueChanged({getMailboxStores})
$myWindow.Controls.Add($ServerNameDrop)
Adding the drop-down manu (ComboBox) works just like adding the label above, until the line after the size specification.  Here we actually invoke an EMS command to get a list of mailbox servers, then add the results to the Items property of the drop-down menu.
The next line is where things really get interesting. Here we tell the $ServerNameDrop object to execute a function (which we've not yet written) called "getMailboxStores" any time the selected value of the drop-down menu changes. Next, we add the object to $myWindow's list of control objects.
Since we've not yet written the "getMailStores" function, if we save and run the script now, then select something from the drop down (which should now be populated with the names of all of the Exchange servers in your organization) we will get an error in the shell saying something like the following:
The term 'getMailboxStores' is not recognized as a cmdlet, function, operable program, or script file. Verify the term and try again.
At E:\Working\scripts\demo.ps1:23 char:59
+ $ServerNameDrop.Add_SelectedValueChanged({getMailboxStores} <<<< )
No biggie. We'll be adding that function shortly. But first, let us create the datastructure into which that function will place its results, then the GUI widget in which that data structure will be displayed.
In the "widget definitions" portion of the script, place the following:

# Add Data Table

$Dataset = New-Object System.Data.DataSet
$storeTable = New-Object System.Data.DataTable
$storeTable.TableName = "Mailbox Stores"
$storeTable.Columns.Add("Store Name")
$storeTable.Columns.Add("Store Size (MB)",[int64])
$Dataset.tables.add($storeTable)
This creates an abstract internal data structure to which our function will return its results, defining its name and the columns it contains.  This then is attached to a meta-structure to which we will later attach an additional table.
Next, we create the GUI widget for displaying this.  This widget is known to .NET programmers as a DataGridView.  The "getMailboxStores" function will actually associate $storeTable with this DataGridView object.
# Add Data Grid View

$myDataGrid = new-object System.windows.forms.DataGridView
$myDataGrid.Location = new-object System.Drawing.Size(10,60)
$myDataGrid.size = new-object System.Drawing.Size(450,500)
$myDataGrid.AllowUserToAddRows = $False
$myDataGrid.AutoSizeColumnsMode = [System.Windows.Forms.DataGridViewAutoSizeColumnsMode]::Fill
$myDataGrid.RowsDefaultCellStyle.BackColor = [System.Drawing.Color]::Bisque
$myDataGrid.AlternatingRowsDefaultCellStyle.BackColor = [System.Drawing.Color]::Beige
$myDataGrid.BorderStyle = [System.Windows.Forms.BorderStyle]::Fixed3D
$myDataGrid.ColumnHeadersDefaultCellSTyle.ForeColor = [System.Drawing.Color]::Maroon
$myDataGrid.ColumnHeadersDefaultCellStyle.BackColor = [System.Drawing.Color]::Tan
$myDataGrid.RowHeadersDefaultCellStyle.BackColor = [System.Drawing.Color]::Tan
$myWindow.Controls.Add($myDataGrid)
As before, we create the object, define its properties (in this case, size and location), then add it to the Controls list of the $myWindow object. I've also illustrated modifying various appearance properties and behaviors here.  Note the syntax for accessing properties and methods for child objects that are not explicitly loaded in the first two lines of the script, as in the case with [System.Drawing.Color].  This syntax could be simplified by explicitly loading each of these class namespaces at the beginning of the script, but I'll leave it to those more knowledgable about the plumbing of .NET to comment on which would be the most efficient approach.
Setting the AllowUserToAddRows property to $False prevents the display of an extraneous blank line at the bottom of the grid. (Try commenting out that line and running the script to see the difference.)  By setting the DataGridViewAutoSizeColumnsMode to Fill, the table data expands to fill the entire grid window.  I would like to have set the FillWeight properties on each column so that less space is occupied by the "Store Size(MB)" column, but I have been unable to determine the proper syntax for doing so within PowerShell. (If you can figure that out, by all means post a comment.)  As it is, the "Store Name" column must be manually expanded to see the entire name for each store.
Now, on to the "getMailboxStores" function.
Near the top of the script, after the two lines which load the GUI assemblies, lets add a new comment to denote the section for containing our functions (for readability), as well as the actual code for our getMailboxStores() function:
# function definitions

function getMailboxStores(){
$storeTable.clear()
$server = $ServerNameDrop.SelectedItem.ToString()
$db = Get-MailboxDatabase -server $server
foreach ($objItem in $db)
{
$edbfilepath = $objItem.edbfilepath
$path = "`\`\" + $server + "`\" +$objItem.EdbFilePath.DriveName.Remove(1).ToString() + "$" + $objItem.EdbFilePath.PathName.Remove(0,2)
$dbsize = Get-ChildItem $path
$storeTable.Rows.add($objItem.Identity,$dbsize.Length/1024KB)
}
$myDataGrid.DataSource = $storeTable

}
After clearing our table structure, we pull the currently-selected server name from the drop-down menu, then perform a Get-MailboxDatabase operation on this server.  The results are stored in an array called $db, then we iterate through the contents of this array to store each database name and size into our table. Then we tell our DataGridView object to use this table at its data source.
Now we have a script which is actually doing something useful.  Let's extend the functionality and make use of some of that window real estate that we have set aside in the right-hand side of the window.  Let us place another grid view in that part of the window, and up by the drop-down menu, add a button to fill that view with a list of mailboxes (and associated mailbox statistics) corresponding to whatever store database is selected. First, find the section of code where we add $storeTable to the list of tables in $Dataset, and add the following after it:
# Add Data Table for mailboxes
$mbTable = New-Object System.Data.DataTable
$mbTable.TableName = "Mailboxes"
$mbTable.Columns.Add("Display Name")
$mbTable.Columns.Add("Account")
$mbTable.Columns.Add("Primary SMTP Address")
$mbTable.Columns.Add("Prohibit Send Quota (MB)",[int64])
$mbTable.Columns.Add("Usage (MB)",[int64])

$Dataset.tables.add($mbTable)
Now we create the DataGridView corresponding to this. After the previous Data Grid View section (just before the main program body), add the following:
# Add Data Grid View for mailboxes

$myDataGrid2 = new-object System.windows.forms.DataGridView
$myDataGrid2.Location = new-object System.Drawing.Size(480,60)
$myDataGrid2.size = new-object System.Drawing.Size(500,500)
$myDataGrid2.AllowUserToAddRows = $False
$myDataGrid2.AutoSizeColumnsMode = [System.Windows.Forms.DataGridViewAutoSizeColumnsMode]::Fill
$myDataGrid2.RowsDefaultCellStyle.BackColor = [System.Drawing.Color]::Bisque
$myDataGrid2.AlternatingRowsDefaultCellStyle.BackColor = [System.Drawing.Color]::Beige
$myDataGrid2.BorderStyle = [System.Windows.Forms.BorderStyle]::Fixed3D
$myDataGrid2.ColumnHeadersDefaultCellSTyle.ForeColor = [System.Drawing.Color]::Maroon
$myDataGrid2.ColumnHeadersDefaultCellStyle.BackColor = [System.Drawing.Color]::Tan
$myDataGrid2.RowHeadersDefaultCellStyle.BackColor = [System.Drawing.Color]::Tan
$myDataGrid2.ColumnHeadersHeightSizeMode = [System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode]::AutoSize
$myWindow.Controls.Add($myDataGrid2)
Note the addition of setting ColumnHeadersHeightSizeMode to AutoSize.  This is to account for the fact that some of our column labels are long enough to wrap around to the next line.
Now we move on to an entirely new GUI widget.  Let us create the button. Right below the code that you just entered, add the following:
# Get Mailboxes Button{

$fsizeButton = new-object System.Windows.Forms.Button
$fsizeButton.Location = new-object System.Drawing.Size(480,30)
$fsizeButton.Size = new-object System.Drawing.Size(120,23)
$fsizeButton.Text = "Get Mailboxes"
$fsizeButton.visible = $True
$fsizeButton.Add_Click({GetMailboxes})
$myWindow.Controls.Add($fsizeButton)
Save and run the script to make sure that each of the interface elements we have created thus far are being rendered properly and are in their proper place.  Of course, if you click the button, an error will be thrown in the shell.  After all, we are invoking a function which we have not yet written, GetMailboxes. Up in the functions section, right after our getMailboxStores function, add the following:
function getMailboxes(){
$mbTable.clear()
$store = $storeTable.DefaultView[$myDataGrid.CurrentCell.RowIndex][0]
write-host "* Enumerating Mailboxes in" $store
$mb = Get-Mailbox -Resultsize Unlimited -Database $store
foreach ($objItem in $mb)
{
if ($objItem.DisplayName -eq $null){$DisplayName=""}
else {$DisplayName = $objItem.DisplayName}
$account=$objItem.SamAccountName
$primary_smtp=$objItem.PrimarySmtpAddress
if ($objItem.ProhibitSendQuota.Value -eq $null){$quota=0}
else {$quota=$objItem.ProhibitSendQuota.Value.ToMB()}
$usage=(Get-MailboxStatistics -Identity  $objItem.Identity).TotalItemSize.Value.ToMB()
write-host $DisplayName, $account, $primary_smtp, $quota, $usage
$mbTable.Rows.add( $DisplayName,$account,$primary_smtp,$quota,$usage )
}
write-host "* Done Enumerating Mailboxes in" $store
$myDataGrid2.DataSource = $mbTable
}
The function starts off by clearing out the table of results from any previous operations, then the RowIndex of the currently selected cell in $myDataGrid is read to determine which mailbox store will be operated upon. A Get-Mailbox command retrieves all of the mailbox objects for the specified database and stores them in an array object called $mb.  We then iterate through each of these mailbox objects, pulling out the Display Name, the SAM Account Name, primary SMTP proxy, the prohibit send quota, then finally dive into a Get-MailboxStatistics command to pull the current usage. These values are then added to a row in $mbTable. Once the iterations are done, $mbTable is specified as the data source for our second data grid view.
If you save and run this script, you'll be able to select a server, select a mailbox store, then hit the button to get a listing of all users in that store. Note that hitting the button without specifying a server will result in an error. I'll leave error handling code for this contingency as an excercise for the reader. Ditto for handling cases where TotalItemSize has not yet been set. (Get-MailboxStatistics fails on mailboxes which have never been logged into.) Also note that, for stores with a large number of mailboxes, it can take a while for any output to appear in the right-hand data grid view.  Nothing actually gets displayed until all of the mailboxes have been iterated through and the datasource assignment at the end of the function above is reached.  However, thanks to the write-host command, we can track the progress of the operation by watching the output in the shell window. Alternatively, we could, if we so chose, add a StatusStrip object along the bottom of the window and display there what account is currently being processed, but I'll leave that as an excercise for the reader.  For an example of this, take a look at http://thepowershellguy.com/blogs/posh/archive/2007/01/21/powershell-gui-scripblock-monitor-script.aspx .
This has actually turned into a pretty handy script for presenting an overview of our Exchange stores.  (I actually wrote it with a work-related task in mind.) You can even click in the column headers to sort the data, all taken care of automatically by .NET.  But what good is all of this if we can't share the results?  Let us address this by adding export functionality, and to trigger these exports, let us introduce yet another common GUI element: the menu strip. Fortunately, we've left some space above the drop-down menu and button.
After the code where we defined our button, add the following:
# Define menus

$myMenuStrip = new-object System.Windows.Forms.MenuStrip
$FileMenu = new-object System.Windows.Forms.ToolStripMenuItem("&File")
$FileExit = new-object System.Windows.Forms.ToolStripMenuItem("&Exit")
$FileExit.add_Click({ $myWindow.close() })
$FileMenu.DropDownItems.Add($FileExit)
$myMenuStrip.Items.Add($FileMenu)
$ExportMenu = new-object System.Windows.Forms.ToolStripMenuItem("&Export")
$ExportStores = new-object System.Windows.Forms.ToolStripMenuItem("&Stores")
$ExportMailboxes = new-object System.Windows.Forms.ToolStripMenuItem("&Mailboxes")
$ExportStores.add_Click({ ExportStoresCSV })
$ExportMailboxes.add_Click({ ExportMailboxesCSV })
$ExportMenu.DropDownItems.Add($ExportStores)
$ExportMenu.DropDownItems.Add($ExportMailboxes)
$myMenuStrip.Items.Add($ExportMenu)
$myWindow.Controls.Add($myMenuStrip)
These interface element definitions are pretty straightforward. We create each element as a new object, define the "on_Click" action for each clickable item (here we encounter two functions which we will define next, ExportStoresCSV and ExportMailboxesCSV), add the menu items to their respective menu items list, attach the menus to the MenuStrip object, then attach the MenuStrip object to the controls list of the window form.
Now, on to the two functions which are triggered by these menu items and actually perform the CSV file exports.  Add the following to the functions segment of the script.
function ExportStoresCSV(){

$exportFileName = new-object System.Windows.Forms.saveFileDialog
$exportFileName.DefaultExt = "csv"
$exportFileName.Filter = "csv files (*.csv)|*.csv"
$exportFileName.InitialDirectory = ".\"
$exportFileName.ShowDialog()
if ($exportFileName.FileName -ne ""){
$exportFile = new-object IO.StreamWriter($exportFileName.FileName,$true)
$exportFile.WriteLine("Store Name,Store Size (MB)")
foreach($row in $storeTable.Rows){
$exportFile.WriteLine("`"" + $row[0].ToString() + "`"," + $row[1].ToString() )
}
$exportFile.Close()
}
}

function ExportMailboxesCSV(){

$exportFileName = new-object System.Windows.Forms.saveFileDialog
$exportFileName.DefaultExt = "csv"
$exportFileName.Filter = "csv files (*.csv)|*.csv"
$exportFileName.InitialDirectory = ".\"
$exportFileName.ShowDialog()
if ($exportFileName.FileName -ne ""){
$exportFile = new-object IO.StreamWriter($exportFileName.FileName,$true)
$exportFile.WriteLine("Display Name,SAM Account Name,Primary SMTP Address,Prohibit Send Quota (MB),Usage (MB)")
foreach($row in $mbTable.Rows){
$exportFile.WriteLine("`"" + $row[0].ToString() + "`",`"" + $row[1].ToString() + "`"," + $row[2].ToString() + "," + $row[3].ToString() + "," + $row[4].ToString() )
}
$exportFile.Close()
}
}
These two functions are essentially identical except for the data table from which they read and the list of items that they write to the csv files.  Each begins by instantiating a saveFileDialog object with the default extension and file browsing filter set to "csv".  Once a file name and save location is set, the file is created and column labels are written to it.  The function then reads through each row of the relevant data table and writes the appropriate column to the file, delimited by commas.  Note that string data should be written to the file wrapped with single quotes lest the text string contain any commas to throw off the delimitation.
That about wraps up this tutorial.  We could go on and add even more functionality, such as making more detailed mailbox information and stats appear when a mailbox is double-clicked, or adding contextual menus which appear when an item is right-clicked exposing actions to perform on the object.  Try modifying widget properties. Try other widgets in the .NET framework. Explore the documentation and experiment. Enjoy.
Full script listing (be careful of column wrapping by the browser):
[System.Reflection.Assembly]::LoadWithPartialName("System.Drawing")
[System.Reflection.Assembly]::LoadWithPartialName("System.windows.forms")

# function definitions

function getMailboxStores(){
$storeTable.clear()
$server = $ServerNameDrop.SelectedItem.ToString()
$db = Get-MailboxDatabase -server $server
foreach ($objItem in $db)
{
$edbfilepath = $objItem.edbfilepath
$path = "`\`\" + $server + "`\" + $objItem.EdbFilePath.DriveName.Remove(1).ToString() + "$" + $objItem.EdbFilePath.PathName.Remove(0,2)
$dbsize = Get-ChildItem $path
$storeTable.Rows.add($objItem.Identity,$dbsize.Length/1024KB)
}
$myDataGrid.DataSource = $storeTable

}

function getMailboxes(){
$mbTable.clear()
$store = $storeTable.DefaultView[$myDataGrid.CurrentCell.RowIndex][0]
write-host "* Enumerating Mailboxes in" $store
$mb = Get-Mailbox -Resultsize Unlimited -Database $store
foreach ($objItem in $mb)
{
if ($objItem.DisplayName -eq $null){$DisplayName=""}
else {$DisplayName = $objItem.DisplayName}
$account=$objItem.SamAccountName
$primary_smtp=$objItem.PrimarySmtpAddress
if ($objItem.ProhibitSendQuota.Value -eq $null){$quota=0}
else {$quota=$objItem.ProhibitSendQuota.Value.ToMB()}
$usage=(Get-MailboxStatistics -Identity $objItem.Identity).TotalItemSize.Value.ToMB()
write-host $DisplayName, $account, $primary_smtp, $quota, $usage
$mbTable.Rows.add( $DisplayName,$account,$primary_smtp,$quota,$usage )
}
write-host "* Done Enumerating Mailboxes in" $store
$myDataGrid2.DataSource = $mbTable
}

function ExportStoresCSV(){

$exportFileName = new-object System.Windows.Forms.saveFileDialog
$exportFileName.DefaultExt = "csv"
$exportFileName.Filter = "csv files (*.csv)|*.csv"
$exportFileName.InitialDirectory = ".\"
$exportFileName.ShowDialog()
if ($exportFileName.FileName -ne ""){
$exportFile = new-object IO.StreamWriter($exportFileName.FileName,$true)
$exportFile.WriteLine("Store Name,Store Size (MB)")
foreach($row in $storeTable.Rows){
$exportFile.WriteLine("`"" + $row[0].ToString() + "`"," + $row[1].ToString() )
}
$exportFile.Close()
}
}

function ExportMailboxesCSV(){

$exportFileName = new-object System.Windows.Forms.saveFileDialog
$exportFileName.DefaultExt = "csv"
$exportFileName.Filter = "csv files (*.csv)|*.csv"
$exportFileName.InitialDirectory = ".\"
$exportFileName.ShowDialog()
if ($exportFileName.FileName -ne ""){
$exportFile = new-object IO.StreamWriter($exportFileName.FileName,$true)
$exportFile.WriteLine("Display Name,SAM Account Name,Primary SMTP Address,Prohibit Send Quota (MB),Usage (MB)")
foreach($row in $mbTable.Rows){
$exportFile.WriteLine("`"" + $row[0].ToString() + "`",`"" + $row[1].ToString() + "`"," + $row[2].ToString() + "," + $row[3].ToString() + "," + $row[4].ToString() )
}
$exportFile.Close()
}
}


# widget definitions

$myWindow = new-object System.Windows.Forms.form

# Add Server Drop Label
$ServerNamelabelBox = new-object System.Windows.Forms.Label
$ServerNamelabelBox.Location = new-object System.Drawing.Size(10,30)
$ServerNamelabelBox.size = new-object System.Drawing.Size(80,20)
$ServerNamelabelBox.Text = "Server Name"
$myWindow.Controls.Add($ServerNamelabelBox)


# Add Server Drop Down Widget
$ServerNameDrop = new-object System.Windows.Forms.ComboBox
$ServerNameDrop.Location = new-object System.Drawing.Size(90,30)
$ServerNameDrop.Size = new-object System.Drawing.Size(100,30)
get-mailboxserver | ForEach-Object{$ServerNameDrop.Items.Add($_.Name)}
$ServerNameDrop.Add_SelectedValueChanged({getMailboxStores})
$myWindow.Controls.Add($ServerNameDrop)

# Add Data Table

$Dataset = New-Object System.Data.DataSet
$storeTable = New-Object System.Data.DataTable
$storeTable.TableName = "Mailbox Stores"
$storeTable.Columns.Add("Store Name")
$storeTable.Columns.Add("Store Size (MB)",[int64])

$Dataset.tables.add($storeTable)

# Add Data Table for mailboxes
$mbTable = New-Object System.Data.DataTable
$mbTable.TableName = "Mailboxes"
$mbTable.Columns.Add("Display Name")
$mbTable.Columns.Add("Account")
$mbTable.Columns.Add("Primary SMTP Address")
$mbTable.Columns.Add("Prohibit Send Quota (MB)",[int64])
$mbTable.Columns.Add("Usage (MB)",[int64])

$Dataset.tables.add($mbTable)

# Add Data Grid View

$myDataGrid = new-object System.windows.forms.DataGridView
$myDataGrid.Location = new-object System.Drawing.Size(10,60)
$myDataGrid.size = new-object System.Drawing.Size(450,500)
$myDataGrid.AllowUserToAddRows = $False
$myDataGrid.AutoSizeColumnsMode = [System.Windows.Forms.DataGridViewAutoSizeColumnsMode]::Fill
$myDataGrid.RowsDefaultCellStyle.BackColor = [System.Drawing.Color]::Bisque
$myDataGrid.AlternatingRowsDefaultCellStyle.BackColor = [System.Drawing.Color]::Beige
$myDataGrid.BorderStyle = [System.Windows.Forms.BorderStyle]::Fixed3D
$myDataGrid.ColumnHeadersDefaultCellSTyle.ForeColor = [System.Drawing.Color]::Maroon
$myDataGrid.ColumnHeadersDefaultCellStyle.BackColor = [System.Drawing.Color]::Tan
$myDataGrid.RowHeadersDefaultCellStyle.BackColor = [System.Drawing.Color]::Tan
$myWindow.Controls.Add($myDataGrid)

# Add Data Grid View for mailboxes

$myDataGrid2 = new-object System.windows.forms.DataGridView
$myDataGrid2.Location = new-object System.Drawing.Size(480,60)
$myDataGrid2.size = new-object System.Drawing.Size(500,500)
$myDataGrid2.AllowUserToAddRows = $False
$myDataGrid2.AutoSizeColumnsMode = [System.Windows.Forms.DataGridViewAutoSizeColumnsMode]::Fill
$myDataGrid2.RowsDefaultCellStyle.BackColor = [System.Drawing.Color]::Bisque
$myDataGrid2.AlternatingRowsDefaultCellStyle.BackColor = [System.Drawing.Color]::Beige
$myDataGrid2.BorderStyle = [System.Windows.Forms.BorderStyle]::Fixed3D
$myDataGrid2.ColumnHeadersDefaultCellSTyle.ForeColor = [System.Drawing.Color]::Maroon
$myDataGrid2.ColumnHeadersDefaultCellStyle.BackColor = [System.Drawing.Color]::Tan
$myDataGrid2.RowHeadersDefaultCellStyle.BackColor = [System.Drawing.Color]::Tan
$myDataGrid2.ColumnHeadersHeightSizeMode = [System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode]::AutoSize
$myWindow.Controls.Add($myDataGrid2)

# Get Mailboxes Button

$fsizeButton = new-object System.Windows.Forms.Button
$fsizeButton.Location = new-object System.Drawing.Size(480,30)
$fsizeButton.Size = new-object System.Drawing.Size(120,23)
$fsizeButton.Text = "Get Mailboxes"
$fsizeButton.visible = $True
$fsizeButton.Add_Click({GetMailboxes})
$myWindow.Controls.Add($fsizeButton)

# Define menus

$myMenuStrip = new-object System.Windows.Forms.MenuStrip
$FileMenu = new-object System.Windows.Forms.ToolStripMenuItem("&File")
$FileExit = new-object System.Windows.Forms.ToolStripMenuItem("&Exit")
$FileExit.add_Click({ $myWindow.close() })
$FileMenu.DropDownItems.Add($FileExit)
$myMenuStrip.Items.Add($FileMenu)
$ExportMenu = new-object System.Windows.Forms.ToolStripMenuItem("&Export")
$ExportStores = new-object System.Windows.Forms.ToolStripMenuItem("&Stores")
$ExportMailboxes = new-object System.Windows.Forms.ToolStripMenuItem("&Mailboxes")
$ExportStores.add_Click({ ExportStoresCSV })
$ExportMailboxes.add_Click({ ExportMailboxesCSV })
$ExportMenu.DropDownItems.Add($ExportStores)
$ExportMenu.DropDownItems.Add($ExportMailboxes)
$myMenuStrip.Items.Add($ExportMenu)
$myWindow.Controls.Add($myMenuStrip)

# main program body

$myWindow.Text = "Exchange 2007 GUI Demo"
$myWindow.size = new-object System.Drawing.Size(1000,600)
$myWindow.autoscroll = $true
#$myWindow.topmost = $true
$myWindow.Add_Shown({$myWindow.Activate()})
$myWindow.ShowDialog()

6 comments:

Arvid said...

Excellent! Thanks for this - I have been struggling with the syntax for some of the windows forms bits and this helped clear things up.

Glen Mark Martin said...

Glad it helped. Be sure to check out Glen Scales' blog (the link is in the article). He has many more examples.

Doron said...

I would like to have set the FillWeight properties on each column so that less space is occupied by the "Store Size(MB)" column,

but I have been unable to determine the proper syntax for doing so within PowerShell


This post really helps me with the understanding of how to crate winform Application with powershell,

and boost my ability - So Glan wish is my Demand .

You have to Add the line Blow & to comma out some line ,I give almost full option you don’t have to use all of them ,

look at Column 3 for minimum requirement.

'DataPropertyName' Should be with the same name as '$mbTable.Columns.Add()' This is the Connection between the Column Data & The Column style.

When you use Column Style you must disable Defaults Row Styles.

See Cell Styles in the Windows Forms DataGridView Control http://msdn.microsoft.com/en-us/library/1yef90x0.aspx)

I didnot add the entire Columns So,When the form is load for the first time its look like below

I limit the width of column 'Prohibit Send..' to 30.


Click on the link to view the picture


This how the form look like after it's pull information. I know it's agley but I want to make a point.


Click on the link to view the picture



# :: # End Part one By Doron Zilber

Doron said...


The Code:

$mbTable.TableName = "Mailboxes"


# Costume Column

# Move from Section -- Add Data Grid View for mailboxes

$myDataGrid2 = new-object System.windows.forms.DataGridView

# Column 1

$myDGTB_1 = New-Object System.Windows.Forms.DataGridViewTextBoxColumn

$myDGTB_1.MaxInputLength = 160

# $myDGTB_1.AutoSizeMode = 6


. . . Column Header = 2

. . . All Cells Except Headr = 4

. . . All Cells = 6

. . . Displayed Cells Except Header = 8

. . . Displayed Cells = 10

. . . Fill = 16


$myDGTB_1.DataPropertyName = "Display name" # <== Connection Shuld Be with the same name as Column Name

$myDGTB_1.DividerWidth = 1

$myDGTB_1.HeaderText = "Display Name"

$myDGTB_1.MinimumWidth = 50

$myDGTB_1.Name = "10"

$myDGTB_1.ToolTipText = "This is The Display Name Column"

$myDGTB_1.Width = 140



# Colum 1 View Style

$myDGTB_1_Style = New-Object System.Windows.Forms.DataGridViewCellStyle

$myDGTB_1_Style.Format = "N0"


. . . Numbers = N2 - 1,000.22 , N0 1,000

. . . Date = dd/mm/yy "d"

. . . Date = D ,f ,d


$myDGTB_1_Style.WrapMode = 2


. . . flase = 1

. . . true = 2


$myDGTB_1_Style.Font = New-Object System.Drawing.Font("Microsoft Sans Serif",14,0,3,177)

$myDGTB_1_Style.ForeColor = "Red"

$myDGTB_1_Style.BackColor = "yellow"

$myDGTB_1_Style.SelectionBackColor = "Green"

$myDGTB_1.DefaultCellStyle = $myDGTB_1_Style



$myDataGrid2.Columns.Add($myDGTB_1)




# :: # End Part tow By Doron Zilber

Doron said...



# 2 Column

$myDGTB_2 = New-Object System.Windows.Forms.DataGridViewTextBoxColumn

$myDGTB_2.MaxInputLength = 160

$myDGTB_2.AutoSizeMode = 6

$myDGTB_2.DataPropertyName = "Account" # <== Connection

$myDGTB_2.DividerWidth = 1

$myDGTB_2.HeaderText = "Account"

$myDGTB_2.MinimumWidth = 50

$myDGTB_2.Name = "10"

$myDGTB_2.ToolTipText = "Doron Zilber and Sigal"

$myDGTB_2.Width = 140



$myDGTB_2_Style = New-Object System.Windows.Forms.DataGridViewCellStyle

$myDGTB_2_Style.Format = "N0" # "d" #N2

$myDGTB_2_Style.WrapMode = 2

$myDGTB_2_Style.Font = New-Object System.Drawing.Font("Microsoft Sans Serif",14,2,3,177)

$myDGTB_2_Style.ForeColor = "Red"

$myDGTB_2_Style.BackColor = "yellow"

$myDGTB_2.DefaultCellStyle = $myDGTB_2_Style



$myDataGrid2.Columns.Add($myDGTB_2)



# 3 Column

$myDGTB_3 = New-Object System.Windows.Forms.DataGridViewTextBoxColumn

$myDGTB_3.DataPropertyName = "Prohibit Send Quota (MB)" # <== Connection

$myDGTB_3.HeaderText = "Prohibit Send Quota (MB)"

$myDGTB_3.Width = 30

$myDataGrid2.Columns.Add($myDGTB_3)

# Costume Column



$mbTable.Columns.Add("Display Name")

$mbTable.Columns.Add("Account")



# Add Data Grid View for mailboxes



# $myDataGrid2 = new-object System.windows.forms.DataGridView

$myDataGrid2.Location = new-object System.Drawing.Size(480,60)

$myDataGrid2.size = new-object System.Drawing.Size(500,500)

$myDataGrid2.AllowUserToAddRows = $False

# $myDataGrid2.AutoSizeColumnsMode = [System.Windows.Forms.DataGridViewAutoSizeColumnsMode]::Fill

# $myDataGrid2.RowsDefaultCellStyle.BackColor = [System.Drawing.Color]::Bisque

# $myDataGrid2.AlternatingRowsDefaultCellStyle.BackColor = [System.Drawing.Color]::Beige

$myDataGrid2.BorderStyle = [System.Windows.Forms.BorderStyle]::Fixed3D

$myDataGrid2.ColumnHeadersDefaultCellSTyle.ForeColor = [System.Drawing.Color]::Maroon



I hope that it will be some help to some one


# :: # End Part tree By Doron Zilber

Doron said...

================================================


Hi the picture i would like to show are below:


I didn't add the entire Columns So,


When the form is load for the first time its look like below


Click here for the picture.


This how the form look like after it's pull information.


I know it's agley but I want to make a point.


Click here for the picture.



from - Doron Zilber
=============================================