Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / PowerShell

PowerShell Application - Password Manager

4.22/5 (4 votes)
21 Jun 2016CPOL5 min read 14.9K  
This article, I'll show you how to start building Applications through PowerShell with help from .NET Framework GUI

Introduction

Who am I?

I'm simply an amature, hobby programmer who like to create and share applications that I make. I've been programming in multiple languages including Java, PHP, HTML/CSS, JS, C# and more. The code compared to what a professional would write will most likely be huge. There will be mistakes, maybe not completely optimized in the best way, but I'm sure that a lot of people can still learn a lot from this.

I feel like PowerShell is very underrated in terms of what it can acheive that other languages can. I write all my code by hand and not in any editor like Sapien or any of such kind. Just simply Notepad.

What will this article tell you?

In this article, I'll demonstrate, and show how I made an application that stores and encrypts passwords.

The Encryption (AES)

For this article, I wont go into detail on the encryption part. I'll focus on building the application.

If you wish to learn more about it, I'd recommend you to read this great Stick Figure Guide on AES

Creating the application

The application, I used the following assemblys. I just put it at the top of the script.

C++
[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Drawing")
[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Security")
[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")
[void] [System.Windows.Forms.Application]::EnableVisualStyles()

After this, I started with building the Form

Full properties information can be found here: https://msdn.microsoft.com/en-us/library/system.windows.forms.form_properties(v=vs.110).aspx

C++
$form = New-Object System.Windows.Forms.Form -Property @{
    Icon = [System.Drawing.Icon]::ExtractAssociatedIcon("C:\Windows\System32\certutil.exe") 
    Text = "Keep passwords safe & encrypted - Powershell" 
    Size = New-Object System.Drawing.Size (555,340)
    FormBorderStyle = "FixedDialog" 
    MaximizeBox = $false 
    KeyPreview = $true 
}

Just simply to test the form, I also added the following

C++
[System.Windows.Forms.Application]::Run($form)

This could've also have been done by writing "$form.ShowDialog()" but I didn't and I'll come back to why.

After I got the size right, and the looks as I wanted. As my next progress I added the main componant. The DataGridView to display all the text boxes and buttons.

Full properties information can be found here: https://msdn.microsoft.com/en-us/library/system.windows.forms.datagridview(v=vs.110).aspx

C++
$grid = New-Object System.Windows.Forms.DataGridView -Property @{
    Size = New-Object System.Drawing.Size (300,250)
    BackgroundColor = "Gray"
    ColumnHeadersHeightSizeMode = "AutoSize"
    AutoSizeRowsMode = "AllCells"
    CellBorderStyle = "None"
    RowHeadersVisible = $false
    ReadOnly = $true
    AllowUserToAddRows = $False
    AllowUserToDeleteRows = $False
    Dock = "Top"
    GridColor = "Black"
}
$form.Controls.Add($grid)

First I docked the Control entirely to Fill the Form, but I changed my mind when I later on wanted to add the textboxes below the control. So I set it to Dock at the Top and I just changed it's size to the appropriate height.

Also modified a lot of properties as I wanted to have the look and features I wanted.

To create the columns to the DataGridView, I made a function for this to manually create them as I wanted.

C++
function CreateColumns()
{
    $column = New-Object System.Windows.Forms.DataGridViewTextBoxColumn -Property @{
        HeaderText = "Name"
        Width = 180
    }
    $grid.Columns.Add($column) | Out-Null
    $column = New-Object System.Windows.Forms.DataGridViewTextBoxColumn -Property @{
        HeaderText = "Password"
        Width = 180
    }
    $grid.Columns.Add($column) | Out-Null
    $column = New-Object System.Windows.Forms.DataGridViewButtonColumn -Property @{
        HeaderText = "View"
        Width = 50
    }
    $grid.Columns.Add($column) | Out-Null
    $column = New-Object System.Windows.Forms.DataGridViewButtonColumn -Property @{
        HeaderText = "Copy"
        Width = 50
    }
    $grid.Columns.Add($column) | Out-Null
    $column = New-Object System.Windows.Forms.DataGridViewButtonColumn -Property @{
        HeaderText = "Delete"
        Width = 50
    }
    $grid.Columns.Add($column) | Out-Null
}

I create first a TextBox column, then another TextBox column followed by 3 Button Columns.

So now whenever I add a row to the DataGridView I get a row with 2 textboxes followed by 3 buttons. Just like the image above shows.

After this. I added the rest of the controls below

Full properties information can be found here: https://msdn.microsoft.com/en-us/library/system.windows.forms.textbox(v=vs.110).aspx

C++
$textBoxMaster = New-Object System.Windows.Forms.TextBox -Property @{
    Location = New-Object System.Drawing.Size (13,253)
    Size = New-Object System.Drawing.Size (403,20)
    PasswordChar = '•'
}
$form.Controls.Add($textBoxMaster)
 
$textBoxName = New-Object System.Windows.Forms.TextBox -Property @{
    Location = New-Object System.Drawing.Size (13,276)
    Size = New-Object System.Drawing.Size (200,20)
}
$form.Controls.Add($textBoxName) 

$textBoxPass = New-Object System.Windows.Forms.TextBox -Property @{
    Location = New-Object System.Drawing.Size (216,276)
    Size = New-Object System.Drawing.Size (200,20)
    PasswordChar = '•'
} 
$form.Controls.Add($textBoxPass) 

Full properties information can be found here: https://msdn.microsoft.com/en-us/library/system.windows.forms.label(v=vs.110).aspx

C++
$labelMaster = New-Object System.Windows.Forms.Label -Property @{
    Location = New-Object System.Drawing.Size (450,256)
    Size = New-Object System.Drawing.Size (100,20)
    Text = "Master key"
} 
$form.Controls.Add($labelMaster) 

Full properties information can be found here: https://msdn.microsoft.com/en-us/library/system.windows.forms.button(v=vs.110).aspx

C++
$buttonAdd = New-Object System.Windows.Forms.Button -Property @{
    Location = New-Object System.Drawing.Size (430,276)
    Size = New-Object System.Drawing.Size (100,20)
    Text = "New secret"
}
$form.Controls.Add($buttonAdd) 

I also added a timer, why I'll go into later in the events section. Full properties information can be found here: https://msdn.microsoft.com/en-us/library/system.windows.forms.timer(v=vs.110).aspx

C++
$timer = New-Object System.Windows.Forms.Timer -Property @{
    Interval = 3000
    Enabled = $false
}

Now when the Form is complete, the applications looks great. But has no function what so ever.

What we need is events, stuff to happen when we press the button/enter text into the textboxes.

Before this however, I added a great piece of code that I used for the encryption/decryption part.

Credits for this all goes to https://gallery.technet.microsoft.com/PowerShell-Script-410ef9df I have just done some minor updates to the code to be compatible with this application.

C++
function Encrypt-String($String, $Passphrase, $salt="SaltCrypto", $init="PassKeeper", [switch]$arrayOutput)
{
# Info: More information and good documentation on these functions can be found in the link above
    $r = New-Object System.Security.Cryptography.RijndaelManaged
    $pass = [Text.Encoding]::UTF8.GetBytes($Passphrase)
    $salt = [Text.Encoding]::UTF8.GetBytes($salt)
    $r.Key = (New-Object Security.Cryptography.PasswordDeriveBytes $pass, $salt, "SHA1", 5).GetBytes(32)
    $r.IV = (New-Object Security.Cryptography.SHA1Managed).ComputeHash( [Text.Encoding]::UTF8.GetBytes($init) )[0..15]
    $c = $r.CreateEncryptor()
    $ms = New-Object IO.MemoryStream
    $cs = New-Object Security.Cryptography.CryptoStream $ms,$c,"Write"
    $sw = New-Object IO.StreamWriter $cs
    $sw.Write($String)
    $sw.Close()
    $cs.Close()
    $ms.Close()
    $r.Clear()
    [byte[]]$result = $ms.ToArray()
    return [Convert]::ToBase64String($result)
}
 
function Decrypt-String($Encrypted, $Passphrase, $salt="SaltCrypto", $init="PassKeeper")
{
    if($Encrypted -is [string])
    {
        $Encrypted = [Convert]::FromBase64String($Encrypted)
    }
 
    $r = New-Object System.Security.Cryptography.RijndaelManaged
    $pass = [Text.Encoding]::UTF8.GetBytes($Passphrase)
    $salt = [Text.Encoding]::UTF8.GetBytes($salt)
    $r.Key = (New-Object Security.Cryptography.PasswordDeriveBytes $pass, $salt, "SHA1", 5).GetBytes(32) #256/8
    $r.IV = (New-Object Security.Cryptography.SHA1Managed).ComputeHash( [Text.Encoding]::UTF8.GetBytes($init) )[0..15]
    $d = $r.CreateDecryptor()
    $ms = New-Object IO.MemoryStream @(,$Encrypted)
    $cs = New-Object Security.Cryptography.CryptoStream $ms,$d,"Read"
    $sr = New-Object IO.StreamReader $cs
    $text = $sr.ReadToEnd()
    $sr.Close()
    $cs.Close()
    $ms.Close()
    $r.Clear()
    return $text
}

OK, after I added these major Functions to the code I made the events.

First off, the button at the bottom.

C++
$buttonAdd.Add_Click({
    if(($textBoxName.Text.Length -gt 0) -and ($textBoxPass.Text.Length -gt 0) -and ($textBoxMaster.Text.Length -gt 0))
    {
        $grid.Rows.Add(($textBoxName.Text), (Encrypt-String -Passphrase $textBoxMaster.Text -String ($textBoxPass.Text) -salt ($textBoxName.Text) -init "$($textBoxName.Text.Length)"))
        $textBoxName.Text = ""
        $textBoxPass.Text = ""
        $grid.ClearSelection()
        Export
    }
})

All the 3 fields at the bottom has to have a text length of greater than 0 for the event to actually do something. Then it creates a row, first column with the text from the textBoxName control. And in the second column the encryption goes. Doesn't matter if this is visible or not. You would still need the master key to decrypt it.

After this, an event for all the buttons in the GridDataView. I use the event called CellContentClick - see https://msdn.microsoft.com/en-us/library/system.windows.forms.datagridview.cellcontentclick(v=vs.110).aspx

C++
$grid.Add_CellContentClick({
    if($grid.CurrentCell.ColumnIndex -ge 2 -and (!$timer.Enabled))
    { # If any of the ButtonColumns are pressed
        switch($grid.CurrentCell.ColumnIndex)
        {
            2 # View column
            {
                try
                {
                    $decrypt = (Decrypt-String -Encrypted ($grid[1,$grid.CurrentCell.RowIndex].Value) -Passphrase $textBoxMaster.Text -salt ($grid[0,$grid.CurrentCell.RowIndex].Value) -init "$(($grid[0,$grid.CurrentCell.RowIndex].Value).Length)")
                    $global:tempArray = @($grid[1,$grid.CurrentCell.RowIndex].Value, $decrypt, $grid.CurrentCell.RowIndex)
                    $grid[1,$grid.CurrentCell.RowIndex].Value = $decrypt
                    $timer.Enabled = $true
                } catch { $textBoxName.Text = "Invalid master key, unable to decrypt." }
            }
            3 # Copy column
            {
                try
                {
                    Decrypt-String -Encrypted ($grid[1,$grid.CurrentCell.RowIndex].Value) -Passphrase $textBoxMaster.Text -salt ($grid[0,$grid.CurrentCell.RowIndex].Value) -init "$(($grid[0,$grid.CurrentCell.RowIndex].Value).Length)" | clip
                } catch { $textBoxName.Text = "Invalid master key, unable to decrypt." }
            }
            4 # Delete column
            {
                $grid.Rows.Remove($grid.Rows[$grid.CurrentCell.RowIndex])
                Export
            }
        }
    }    
    $grid.ClearSelection()
})

This triggers every click in the datagridview, but I only choose to be interested in clicks from the 3 last columns (the buttons). I used a switch statement to see which button was pressed and coded what happend on every button.

The View column sets a variable called "decrypt" to the decrypted text, and an array of the original encrypted text. Then changes the same rows 2nd column to the visible text and enables a timer that changes back the textbox to the original encrypted text after 3 seconds.

C++
$timer.Add_Tick({
    try
    {
        $grid[1, $Global:tempArray[2]].Value = $Global:tempArray[0]
    } catch { }
    $timer.Enabled = $false
})

The Copy button simply decrypts the encrypted text and I pipelines it clip.exe to hold it onto as Ctrl+C

And finaly the delete button removes the selected Row.

I also made a feature to save and export the keys so you dont have to reenter the passwords everytime the application reopens.

C++
function Export()
{
# Info: Exports all relevent cells to a .dat file in the %temp% directory
    if(Test-Path "$env:LOCALAPPDATA\Temp\PassKeeper.dat")
    {
        Remove-Item "$env:LOCALAPPDATA\Temp\PassKeeper.dat" -Force -Confirm:$false | Out-Null
    }
    for($i=0; $i -lt $grid.RowCount; $i++)
    {
        $item = $grid.Rows[$i].Cells.Value
        "$($item[0])→$($item[1])" | Out-File "$env:LOCALAPPDATA\Temp\PassKeeper.dat" -Append
    }
}

function Import()
{
# Info: Imports .dat file, if it exists to retreive old saved secrets
    if(Test-Path "$env:LOCALAPPDATA\Temp\PassKeeper.dat")
    {
        $grid.RowCount = 0
        GC "$env:LOCALAPPDATA\Temp\PassKeeper.dat" | % {
            $grid.Rows.Add("$($_.Split("")[0])", "$($_.Split("")[1])")
        }
    }
}

The Import function is called at the start of the application, and the Export everytime the datagridview changes in any way.

The functions work in the way that they checks every row and collects the data of the column1 and column2 and appends this to a file with an " → " for an easy way to split this back in the application in the Import function.

Import reads the file, if it exists and creates a row in the datagridview from every row in the file split by the " → " character.

Another function I like to use is one to hide the Console window so that it's not visible.

C++
function HideConsole($show=$false)
{
# Info: Hides the console window
# Params: show, if I would like to show the console again. Just add the parameter -show:$true
    if($show) { $v = 5 } else { $v = 0 }
    Add-Type -Name Window -Namespace Console -MemberDefinition '
[DllImport("Kernel32.dll")]
public static extern IntPtr GetConsoleWindow();
 
[DllImport("user32.dll")]
public static extern bool ShowWindow(IntPtr hWnd, Int32 nCmdShow);
'
    $consolePtr = [Console.Window]::GetConsoleWindow()
    [Console.Window]::ShowWindow($consolePtr, $v)
}

I call this Function at the start of the application in order to hide the console window.

Also for a finish touch, I added a NotifyIcon together with if you minimize the application. It hides from the taskbar. Along with a contextmenu on the NotifyIcon to show it again or Exit the application.

C++
$IconMenu = New-Object System.Windows.Forms.ContextMenu
$IconMenuExit = New-Object System.Windows.Forms.MenuItem
$IconMenuShow = New-Object System.Windows.Forms.MenuItem -Property @{
    Text = "Show"
}
$IconMenuExit.Text = "E&xit"
$IconMenu.MenuItems.Add($IconMenuShow) | Out-Null
$IconMenu.MenuItems.Add($IconMenuExit) | Out-Null
$Notify = New-Object System.Windows.Forms.NotifyIcon -Property @{
    Icon = [System.Drawing.Icon]::ExtractAssociatedIcon("C:\Windows\System32\certutil.exe")
    BalloonTipTitle = "KeePass"
    BalloonTipIcon = "Info"
    Visible = $true
    ContextMenu = $IconMenu
}

Together with the triggering events

C++
$IconMenuExit.add_Click({
    $form.Close()
    $Notify.visible = $false
})

$form.Add_Closing(
{
    $Notify.visible = $false
})

$form.Add_Resize(
{
    if($form.WindowState -eq "Minimized")
    {       
        $form.ShowInTaskbar = $false
        $form.Visible = $false
        $textBoxMaster.Text = ""
    }
})

$IconMenuShow.add_Click({
    $form.ShowInTaskbar = $true
    $form.Visible = $true
    $form.WindowState = "Normal"
})

Last words

I hope that someone found this article/code interesting. As I stated in the beginning. I'm not a professional programmer of any kind. Simply an hobbyist that find it very entertaining and useful in all kinds of situations.

I'll upload the code. There's more comments in the code/regions so you'll see how I organized the script. Feel free to use it in any way you want.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)