[PowerShell] Create Winform by PowerShell

หลังจากที่ได้ลองเล่น Power Shell มาสักพักแล้ว ผมมี idea ขึ้นมา เนื่องจาก Workshop ที่แล้ว ผมได้ทำบน Console ซึ่งอาจจะไม่สะดวกมากนั้น คราวนี้ผมลองสร้างเป็น Winform เล็กๆขึ้นมาแทนครับ(จริงๆ กะลองกับ C# ก่อน แต่มาเจอการใช้ประยุกต์ใช้งานแบบนี้ เลยลองมาเล่นก่อน 555) มาถึงตรงนี้หลายๆคนอาจะมีคำถาม ผมเลยทำ Q/A สรุปได้เลยครับ

Q: Power Shell มีความสามารถในการสร้าง Winform ด้วยเหรอ ?
A: มีครับ เนื่องจากตัว PowerShell เองมีพื้นฐานมาจาก .Net Framework ทำให้สามารถเรียกใช้ library ของ Winform ได้ครับ

Q: ต้องสร้าง Power Shell จาก Command ตรงๆเลย หรือ ไม่มี Tools ลากวางแบบ Visual Studio ?
A: มีครับ โดย Tools ชื่อ Power Shell Studio มีทั้ง Version Community(ฟรี) และ License(เสียตังค์) แต่ในบทความนี้ผมของ Hard Code นะครับ อิอิ

PowerShell Studio คือ โปรแกรมที่ทำหน้าที่คล้ายกับ Microsoft Visual Studio โดยเป็น IDE ที่ช่วยให้ Dev อย่างเราพัฒนาโปรแกรมได้รวดเร็วขึ้นครับ
PowerShell Studio คือ โปรแกรมที่ทำหน้าที่คล้ายกับ Microsoft Visual Studio โดยเป็น IDE ที่ช่วยให้ Dev อย่างเราพัฒนาโปรแกรมได้รวดเร็วขึ้นครับ

ใน Winform ที่ผมเขียนขึ้น จะมี Control ที่จำเป็นต่างๆ ในการรับค่า Parameter ต่างๆ ได้แก่ Textbox กับ MaskTextBox และการแสดงผลนั้น ผมใช้ตัว DataGridView ครับ โดยหน้าตาของโปรแกรมที่ได้มีลักษณะ ดังนี้
2014-09-26_5-47-01
หลังจากเห็น Output แล้ว เรามาลองดู Code ที่ละส่วนกัน โดยผมแยกเป็นส่วนของ UI กับ Logic นะครับ

  • ส่วนแรกเลย ส่วนของ UI โดยผมของอธิบาย Code ที่ละจุด ดังนี้
    • ขั้นแรก คือ การบอกให้ PowerShell มันรู้ว่า เราใช้ Library ของ .Net Framework มาช่วยในการสร้าง UI("System.Drawing", "System.Windows.Forms" และ System.ComponentModel) และใช้ Collection อย่าง DataTable ("System.Data") นะครับ
      [void] [System.Reflection.Assembly]::LoadWithPartialName("System.Drawing")
      [void] [System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")
      [void] [System.Reflection.Assembly]::LoadWithPartialName("System.ComponentModel")
      [void] [System.Reflection.Assembly]::LoadWithPartialName("System.Data")
      
    • ขั้นที่สอง เริ่มสร้าง Form กันก่อน โดยใช้ Code ดังนี้
      $frmMain = New-Object System.Windows.Forms.Form
      $frmMain.Size = New-Object System.Drawing.Size(550,400)
      $frmMain.FormBorderStyle = "FixedSingle"
      $frmMain.MaximizeBox = $false
      $frmMain.MinimizeBox = $false;
      $frmMain.StartPosition = "CenterScreen"
      
      $frmMain.Add_Shown({$frmMain.Activate()})
      [void] $frmMain.ShowDialog()   #activating the form
      

      ลอง Run Code ดู พบว่ามันแสดง Winform ได้แล้ว ดังรูป
      2014-09-26_5-48-03

    • ขั้นที่สาม ลองเพิ่ม Label และ Textbox ดังนี้
      #BEGIN ---BASE PATH---
      $lblBasePath = New-Object System.Windows.Forms.Label
      $lblBasePath.Location = New-Object System.Drawing.Size(10,40)
      $lblBasePath.Size = New-Object System.Drawing.Size(180,20)
      $lblBasePath.Text = "Please Enter Base Path to Search:"
      $frmMain.Controls.Add($lblBasePath) 
      
      $txtFilePath = New-Object System.Windows.Forms.TextBox
      $txtFilePath.Location = New-Object System.Drawing.Size(200,38)
      $txtFilePath.Size = New-Object System.Drawing.Size(300,20)
      $frmMain.Controls.Add($txtFilePath)
      #END ---BASE PATH---   
      
      #BEGIN ---File Name---
      $lblFileName = New-Object System.Windows.Forms.Label
      $lblFileName.Location = New-Object System.Drawing.Size(10,80)
      $lblFileName.Size = New-Object System.Drawing.Size(180,20)
      $lblFileName.Text = "Please Enter File Name to Search:"
      $frmMain.Controls.Add($lblFileName) 
      
      $txtFileName = New-Object System.Windows.Forms.TextBox
      $txtFileName.Location = New-Object System.Drawing.Size(200,76)
      $txtFileName.Size = New-Object System.Drawing.Size(300,20)
      $frmMain.Controls.Add($txtFileName)
      #END ---File Name---
      
    • ขั้นที่สี่ ลองเพิ่มลูกเล่น โดยเปลี่ยนจาก Textbox มาใช้ MaskTextbox แทน เพือกรองการกรอกข้อมูลของ User ให้ไป pattern ตามที่เราต้องการ ในกกรณีนี้ ผมได้กำหนด Mask หรือ Pattern เป็นรูปแบบของ Version ที่ใช้กัน เช่น 6.00.0004 เป็นต้น
      #BEGIN ---Version---
      $lblVersion = New-Object System.Windows.Forms.Label
      $lblVersion.Location = New-Object System.Drawing.Size(10,120)
      $lblVersion.Size = New-Object System.Drawing.Size(180,20)
      $lblVersion.Text = "Please Enter version to Search:"
      $frmMain.Controls.Add($lblVersion) 
      
      $maskVersion = New-Object System.Windows.Forms.maskedtextbox
      $maskVersion.Location = New-Object System.Drawing.Size(200,114)
      $maskVersion.Size = New-Object System.Drawing.Size(300,20)
      $maskVersion.mask = "0.00.0000"
      $frmMain.Controls.Add($maskVersion)
      #END ---Version---
      
    • ขั้นทีห้า เพิ่ม Code และการจัดการ Event เกี่ยวกับปุ่ม โดยตอนนี้ จะยังมีปุ่ม Search(มีการทดสอบใช้ Try Catch ด้วย) และ Clear
      #BEGIN --- Search BUTTON---
      $btnSearch = New-Object System.Windows.Forms.Button
      $btnSearch.Location = New-Object System.Drawing.Size(200,150)
      $btnSearch.Size = New-Object System.Drawing.Size(60,30)
      $btnSearch.Text = "Search"
      $btnSearch.Add_Click(
          {
              Try
              {
                  FindFilesbyVersion $txtFilePath.Text $txtFileName.Text $maskVersion.Text
              }
              Catch
              {
                  $ErrorMessage = $_.Exception.Message
                  $FailedItem = $_.Exception.ItemName
                  [System.Windows.Forms.MessageBox]::Show("Failed operation on $FailedItem. The error message was $ErrorMessage" , "Error Message")
                  Write-Host "Failed operation on $FailedItem. The error message was $ErrorMessage"
              }
          }
      )
      $frmMain.Controls.Add($btnSearch)
      #END ---Search BUTTON---
      
      #BEGIN --- CLAER BUTTON---
      $btnClear = New-Object System.Windows.Forms.Button
      $btnClear.Location = New-Object System.Drawing.Size(280,150)
      $btnClear.Size = New-Object System.Drawing.Size(60,30)
      $btnClear.Text = "Clear"
      $btnClear.Add_Click({ClearForm})
      $frmMain.Controls.Add($btnClear)
      #END ---CLEAR BUTTON---
      
    • ขั้นที่หก พระเอกของโปรแกรมนี้เลย คือ ส่วนของ DataGridView ที่แสดงผลลัพธ์ตามเงื่อนไขที่ User ได้กรอกเข้ามาครับ
      #BEGIN --- Data Grid---
      $dgvSearchResult = New-Object System.Windows.Forms.DataGridView
      $dgvSearchResult.Location = New-Object System.Drawing.Point(10,200)
      $dgvSearchResult.Size = New-Object System.Drawing.Size(500, 150)
      $dgvSearchResult.AllowUserToAddRows = $False
      $dgvSearchResult.AllowUserToDeleteRows = $False
      $dgvSearchResult.ReadOnly = $False
      $dgvSearchResult.Visible = $true
      $dgvSearchResult.ColumnHeadersHeightSizeMode = 'AutoSize'
      $dgvSearchResult.AutoResizeColumns( "AllCells" )
      $frmMain.Controls.Add($dgvSearchResult)
      #END --- Data Grid---
      
  • ส่วนที่สอง เป็นส่วนของ Logic โดยผมได้ต่อยอดจาก Code ชุดเดิมที่ได้เขียนเอาไว้ เมื่อครั้งที่แล้ว แต่คราวนี้ปรับเปลี่ยนเป็น Function เลย โดย Code ในส่วนนี้จะมี Function 2 อันได้แก่ FindFilesbyVersion(เอาไว้หาข้อมูลไฟล์ โดยใช้เลข Version) และ ClearForm(เอาไว้เคลียร์ข้อมูลครับ)
    function FindFilesbyVersion
    {
        param([string] $filePath,[string] $fileName,[string] $fileVersion)
    
        $resultDT = New-Object system.Data.DataTable
        $col1 = $resultDT.Columns.Add( "ParentPath", [string]) | Out-Null
        $col2 = $resultDT.Columns.Add( "FileName", [string]) | Out-Null
        $col3 = $resultDT.Columns.Add( "FileVersion", [string]) | Out-Null
        $col4 = $resultDT.Columns.Add( "Productversion", [string]) | Out-Null
        $col5 = $resultDT.Columns.Add( "ProductName", [string]) | Out-Null  
    
        $searchDataArr  = Get-ChildItem $filePath -recurse -Include *.exe, *.ocx, *.dll -ErrorAction Stop |
                    Where-Object {(($_.VersionInfo).FileVersion -eq $fileVersion) -and ($_.Name -like '*'+$fileName+'*')} |
                    Select-Object -ExpandProperty VersionInfo |
                    Select-Object -Property @{l='ParentPath';e={Split-Path $_.FileName}},FileName, FileVersion, Productversion, ProductName
    
        foreach ($searchData in $searchDataArr)
        {
            $resultDT.Rows.Add( $searchData.ParentPath, $searchData.FileName, $searchData.FileVersion , $searchData.Productversion ,  $searchData.ProductName ) | Out-Null
        }
        $dgvSearchResult.DataSource = $resultDT
    }
    
    function ClearForm
    {
        $txtFilePath.Text = ""
        $txtFileName.Text = ""
        $maskVersion.text = ""
    }
    

รวม Code ทั้งหมดเข้าด้วยกัน

<# 
        "FindFilesbyVersion"     
             
        Author  : Chatri Ngambenchawong
        Website : https://naiwaen.debuggingsoft.com/ 
        Facebook: https://www.facebook.com/pingkunga
        Twitter : https://twitter.com/pingkunga

        Date    : 21-SEP-2014 
        File    : FindFilesbyVersion 
        Purpose : FInd Files by Version Using Powershell 
           
        Version : 2 (PowerShell GUI)
#> 
Set-ExecutionPolicy RemoteSigned
[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Drawing") 
[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")  
[void] [System.Reflection.Assembly]::LoadWithPartialName('System.ComponentModel') 
[void] [System.Reflection.Assembly]::LoadWithPartialName('System.Data')

$frmMain = New-Object System.Windows.Forms.Form    
$frmMain.Size = New-Object System.Drawing.Size(550,400) 
$frmMain.FormBorderStyle = "FixedSingle"
$frmMain.MaximizeBox = $false
$frmMain.MinimizeBox = $false;
$frmMain.StartPosition = "CenterScreen"

#BEGIN ---BASE PATH---
$lblBasePath = New-Object System.Windows.Forms.Label
$lblBasePath.Location = New-Object System.Drawing.Size(10,40) 
$lblBasePath.Size = New-Object System.Drawing.Size(180,20) 
$lblBasePath.Text = "Please Enter Base Path to Search:"
$frmMain.Controls.Add($lblBasePath) 

$txtFilePath = New-Object System.Windows.Forms.TextBox
$txtFilePath.Location = New-Object System.Drawing.Size(200,38) 
$txtFilePath.Size = New-Object System.Drawing.Size(300,20) 
$frmMain.Controls.Add($txtFilePath)  
#END ---BASE PATH---   

#BEGIN ---File Name---
$lblFileName = New-Object System.Windows.Forms.Label
$lblFileName.Location = New-Object System.Drawing.Size(10,80) 
$lblFileName.Size = New-Object System.Drawing.Size(180,20) 
$lblFileName.Text = "Please Enter File Name to Search:"
$frmMain.Controls.Add($lblFileName) 

$txtFileName = New-Object System.Windows.Forms.TextBox
$txtFileName.Location = New-Object System.Drawing.Size(200,76) 
$txtFileName.Size = New-Object System.Drawing.Size(300,20) 
$frmMain.Controls.Add($txtFileName)  
#END ---File Name---      


#BEGIN ---Version---
$lblVersion = New-Object System.Windows.Forms.Label
$lblVersion.Location = New-Object System.Drawing.Size(10,120) 
$lblVersion.Size = New-Object System.Drawing.Size(180,20) 
$lblVersion.Text = "Please Enter version to Search:"
$frmMain.Controls.Add($lblVersion) 

$maskVersion = New-Object System.Windows.Forms.maskedtextbox
$maskVersion.Location = New-Object System.Drawing.Size(200,114) 
$maskVersion.Size = New-Object System.Drawing.Size(300,20) 
$maskVersion.mask = "0.00.0000"
$frmMain.Controls.Add($maskVersion)  
#END ---Version---                 

#BEGIN --- Search BUTTON---
$btnSearch = New-Object System.Windows.Forms.Button 
$btnSearch.Location = New-Object System.Drawing.Size(200,150) 
$btnSearch.Size = New-Object System.Drawing.Size(60,30) 
$btnSearch.Text = "Search" 
$btnSearch.Add_Click(
    {
        Try
        {
            FindFilesbyVersion $txtFilePath.Text $txtFileName.Text $maskVersion.Text 
        }
        Catch
        {
            $ErrorMessage = $_.Exception.Message
            $FailedItem = $_.Exception.ItemName
            #[System.Windows.Forms.MessageBox]::Show("Failed operation on $FailedItem. The error message was $ErrorMessage" , "Error Message") 
            Write-Host "Failed operation on $FailedItem. The error message was $ErrorMessage"
        }
    }
) 
$frmMain.Controls.Add($btnSearch) 
#END ---Search BUTTON---

#BEGIN --- CLAER BUTTON---
$btnClear = New-Object System.Windows.Forms.Button 
$btnClear.Location = New-Object System.Drawing.Size(280,150) 
$btnClear.Size = New-Object System.Drawing.Size(60,30) 
$btnClear.Text = "Clear" 
$btnClear.Add_Click({ClearForm}) 
$frmMain.Controls.Add($btnClear) 
#END ---OK BUTTON---

#BEGIN --- Data Grid---
$dgvSearchResult = New-Object System.Windows.Forms.DataGridView
$dgvSearchResult.Location = New-Object System.Drawing.Point(10,200) 
$dgvSearchResult.Size = New-Object System.Drawing.Size(500, 150) 
$dgvSearchResult.AllowUserToAddRows = $False
$dgvSearchResult.AllowUserToDeleteRows = $False 
$dgvSearchResult.ReadOnly = $False 
$dgvSearchResult.Visible = $true
$dgvSearchResult.ColumnHeadersHeightSizeMode = 'AutoSize'
$dgvSearchResult.AutoResizeColumns( "AllCells" )
$frmMain.Controls.Add($dgvSearchResult) 
#END --- Data Grid---

$frmMain.Add_Shown({$frmMain.Activate()})
[void] $frmMain.ShowDialog()   #activating the form 

function FindFilesbyVersion
{
    param([string] $filePath,[string] $fileName,[string] $fileVersion)
    
    $resultDT = New-Object system.Data.DataTable
    $col1 = $resultDT.Columns.Add( "ParentPath", [string]) | Out-Null
    $col2 = $resultDT.Columns.Add( "FileName", [string]) | Out-Null  
    $col3 = $resultDT.Columns.Add( "FileVersion", [string]) | Out-Null
    $col4 = $resultDT.Columns.Add( "Productversion", [string]) | Out-Null 
    $col5 = $resultDT.Columns.Add( "ProductName", [string]) | Out-Null  
   
    $searchDataArr  = Get-ChildItem $filePath -recurse -Include *.exe, *.ocx, *.dll -ErrorAction Stop |
                Where-Object {(($_.VersionInfo).FileVersion -eq $fileVersion) -and ($_.Name -like '*'+$fileName+'*')} |
                Select-Object -ExpandProperty VersionInfo |
                Select-Object -Property @{l='ParentPath';e={Split-Path $_.FileName}},FileName, FileVersion, Productversion, ProductName
 
    foreach ($searchData in $searchDataArr)            
    {  
        $resultDT.Rows.Add( $searchData.ParentPath, $searchData.FileName, $searchData.FileVersion , $searchData.Productversion ,  $searchData.ProductName ) | Out-Null
    }
    $dgvSearchResult.DataSource = $resultDT
}


function ClearForm 
{
    $txtFilePath.Text = ""
    $txtFileName.Text = ""
    $maskVersion.text = ""
}

ผลลัพธ์ที่ได้ (ขอปิดข้อมูลบางส่วนนนะ เพราะใช้ของบริษัท ขี้เกียจมาทำข้อมูลทดสอบลองของจริงเลย)
2014-09-24_0-43-39


Discover more from naiwaen@DebuggingSoft

Subscribe to get the latest posts to your email.