Author Archives: Mike

OwnerDraw ListView

Introduction

I am writing a small PowerShell tool that will perform a few checks against several virtual machines in vCenter.  I wanted to show the results in a nice grid view.  The ListView control is the easy choice for this.

 

Basic Grid

This picture below shows the basic layout that I started with (with random dummy data), and as you can see it’s not great.

The ‘P’ass and ‘F’ail display could be better, icons would be great.  The problem is that the basic ListView does not support more than one icon in the grid – usually the first one next to the name.

There are some tweaks you can do to move the single icon about by changing columns, but it’s still just one icon.

OwnerDraw

With a lot of the Windows forms controls they support an OwnerDraw method.  This allows developers to hack around with controls and make them do things that they can’t normally do – like having more that one icon in a ListView, including its subitems.  🙂

Looks great doesn’t it.!

It works by replacing “P” or “F” in the top screenshot with “ICON|x” where “x” is the index number of the image you want to use.  For example, my ticks are in position 1, and the crosses are position 2.  Therefore “ICON|1” or “ICON|2” would be used.  My code has an additional option of displaying an icon and text in the same box.  To show this use “BOTH|x|text”

The Code

In order to change a standard ListView into an awsome ListView does not require a lot of code.

about 50 lines of additional code for the OwnerDraw part, and 3x lines in the middle of the ListView definition.

ListView Definition

# Define ListView Control
$lstList                      = New-Object 'System.Windows.Forms.ListView'

# Add OwnerDraw Code
$lstList.OwnerDraw            = $True
$lstList.Add_DrawSubItem($SubIcons_DrawSubItem)
$lstList.Add_DrawColumnHeader($SubIcons_DrawColumnHeader)

# Set ListView properties
$lstList.Location             = ' 43,  12'
$lstList.Size                 = '589, 208'
$lstList.LabelEdit            = $True
$lstList.FullRowSelect        = $True
$lstList.GridLines            = $True
$lstList.HideSelection        = $True
$lstList.HeaderStyle          = 'Nonclickable'
$lstList.SmallImageList       = $imgResult
$lstList.Sorting              = 'Ascending'
$lstList.View                 = 'Details'

# Add first two columns
$lstList_CH_1                 = New-Object 'System.Windows.Forms.ColumnHeader'
$lstList_CH_2                 = New-Object 'System.Windows.Forms.ColumnHeader'
$lstList_CH_1.Text            = 'Virtual Machine Name'
$lstList_CH_2.Text            = 'Status'
$lstList_CH_1.Width           = 244
$lstList_CH_2.Width           = 100
$lstList_CH_2.TextAlign       = 'Center'
$lstList.Columns.Add($lstList_CH_1) | Out-Null
$lstList.Columns.Add($lstList_CH_2) | Out-Null

# Add 'results' columns
For ($i=0; $i -le 7; $i++)
{
    $newCH           = New-Object 'System.Windows.Forms.ColumnHeader'
    $newCH.Text      = 'RESULTS'.Substring($i, 1)
    $newCH.Width     = 32
    $newCH.TextAlign = 'Center'
    $lstList.Columns.Add($newCH) | Out-Null
}

# Add control to the main form
$MainFORM.Controls.Add($lstList)

Most of this code you should already know if you write PowerShell Forms.  Lines 05,06 and 07 are the new ones that you need for the OwnerDraw parts.

OwnerDraw Code

This next code block has two functions, they are explained below.

$SubIcons_DrawColumnHeader = {
    [System.Windows.Forms.DrawListViewColumnHeaderEventArgs]$e = $_
    $e.DrawDefault = $True
    $e.DrawBackground()
    $e.DrawText()
}

$SubIcons_DrawSubItem = {
    [System.Windows.Forms.DrawListViewSubItemEventArgs]$e = $_

    If ($e.SubItem.Text.Length -le 5) { $e.DrawDefault = $True }
    Else
    {
        If ($e.SubItem.Text.Contains('|') -eq $True)
        {
            [System.Drawing.Image]$icon = ($imgResult.Images[$e.SubItem.Text.Split('|')[1] -as [int]])
            [int]$xPos = ($e.SubItem.Bounds.X + (($e.SubItem.Bounds.Width  / 2) -as [int]) - (($icon.Width  /2) -as [int]))
            [int]$yPos = ($e.SubItem.Bounds.Y + (($e.SubItem.Bounds.Height / 2) -as [int]) - (($icon.Height /2) -as [int]))
        }

        Switch ($e.SubItem.Text.Substring(0,5).ToUpper())
        {
            'ICON|'
            {
                $r = New-Object 'System.Drawing.Rectangle'($xPos, $yPos, $icon.Width, $icon.Height)

                $e.DrawDefault = $false
                $e.Graphics.DrawImage($icon, $r)
            }

            'BOTH|'
            {
                [int]$fPos = ((($e.SubItem.Bounds.Height - $sysFont.Height) / 2) -as [int])
                $fColor    = New-Object 'System.Drawing.SolidBrush'($e.SubItem.ForeColor)

                $rI = New-Object 'System.Drawing.Rectangle'($($e.SubItem.Bounds.X + 3), $yPos, $icon.Width, $icon.Height)
                $rS = New-Object 'System.Drawing.PointF'   ($($e.SubItem.Bounds.X + 5 + $icon.Width), $($e.SubItem.Bounds.Y + $fPos))

                $e.DrawDefault = $false
                $e.Graphics.DrawImage($icon, $rI)
                $e.Graphics.DrawString($e.SubItem.Text.Split('|')[2], $sysFont, $fColor, $rS)
            }

            Default { $e.DrawDefault = $True }
        }
    }
}

The first SubIcons_DrawColumnHeader can largely be ignored.  It’s needed to draw the default column headers.

The second is the main worker.  It turns the text “ICON|x” into the image to be used, and displays it centred in the subitem window.  It also displays “BOTH|x|text” with the icon on the left with the text left-aligned next to it.

Conclusion

Obviously there is a lot of code missing that is used in the screen shots, but the important part is there in order to get you up and running.  Have a play, and let me know what you think in the comments below.

A full demo can be found on my GitHub

Server QA Scripts – Settings Configuration Tool

Introduction

Following on from my Server QA Scripts post, I have since created a GUI form to make the configuration a lot easier to do.  The tool is fully written in PowerShell and is uploaded to my GitHub page.

To Use

Introduction

To use, just open a PowerShell windows and start the `QA-Settings-Configurator.ps1` script.

You’ll be presented with the following window.

QA GUI Page 1

Click the Set Check Location button and location the folder where your scripts are located.  Once done, you will be able to change the language (if any) and a base settings file (if any others exist).  Click Import Settings when done.

The script will then scan the scripts folder and load the selected settings file.

Select Required Checks

The next page lists all the checks available and automatically selects all the checks enabled in the settings file.

QA GUI Page 2

You can select or deselect any check that you feel best suits your environment.  For example, if you don’t use McAfee Antivirus in your environment, you wouldn’t want to check for the McAfee Antivirus agent, therefore, you would deselect the COM01 check.

Once you have finished your selections, click the Next > button.

QA Check Values

The third page has several tabs, one tab for each of the check sections.  Within each of the these tabs is a list of the checks you selected, as well as the required settings for each check.

QA GUI Page 3

Double-click each of the settings and change the values that you require for your environment, remembering to check the settings in each of the tabs.

When you are happy with your settings, select the final page.

Generate QA

QA GUI Page 4

Now that you have completed your changes, enter a short code for your QA script file.  This will be the unique code that the QA compiler will use to name your QA script file.  For example, a short code of “ACME” will generate a QA script called “QA_ACME_v3.xx.xxxx.ps1“.  Also enter a company name for the HTML reports that are generated.

Click Save Settings when you are ready, and enter a filename to save your settings as.  Generate a compiled QA script from this page too by clicking the Generate QA Script button.

 

Other functions

If you happen to loose your settings file, you can restore them by clicking the Restore Settings File button.  Select the location of a compiled QA script and the function will recreate the settings INI file for you.

 

Server QA Scripts

Introduction

At the start of 2015 my company was still building a lot of new Windows servers manually (physical and virtual).  Each of these servers had a check sheet of what was completed and what wasn’t.  Sometimes stuff would be missed for one reason or another, which is why the servers typically went through a process of QA (Quality Assurance).

This QA check was a time consuming manual process that involved someone logging into each server and checking that everything was built and configured as it should be, as well as making sure all the correct tooling was installed (AV, SCOM and SCCM agents, etc).  This typically took a good 2 hours per server.  Results were filled in to an Excel spreadsheet and filed away somewhere as an audit trail.

 

My Solution

Fast forward to today, and those servers are now mostly built via our in-house automation process, however there is still some manual configuration that is required.  The QA process has also improved.  I have taken the manual spreadsheet and converted it into separate PowerShell scripts that are ‘compiled’ into one large script, that is just shy of 8000 lines.!

This script takes less than a minute to complete over 70 checks and write a HTML report for each viewing

My scripts are currently at version 3 and have been released into the wild so that other people can take advantage of the hard work I  put in to create them.

 

The Scripts

I have spent the best part of 18 months creating and tweaking these scripts.  I started with a broken system of about 40 QA checks and got them working.  I then expanded and enhanced the number and quality of the scripts as well as the underlying engine.

As of today (2016-09-06) there are 75 checks.  Hopefully with some community input and help we can increase that.

 

GitHub Repository

All the code is available for anyone to play with on my GitHub repository: https://github.com/My-Random-Thoughts/Server-QA-Checks

 

Screen Shot

FullScan-OneServer

A full QA scan against one server

 

 

 

 

 

 

 

 

Out-FancyHTML Function

Introduction

For a larger project of mine, I needed to produce some fancy looking HTML reports from the data I collected.  An example of this output can be seen below.:

FancyHTML

 

The screen shot shows the results of the code below running on my local laptop:

Get-Process | Select Name, CPU, Handles, Path, Company, FileVersion | Sort Name

 

Description

The code takes the results of any table based output, formats it as a HTML table, then runs various colour highlighting on specific cells depending on the rules you specify.

For example, the following code will find any cell in the “Handles” column and checks the values found.  If the value is between 50 and 100, it will be coloured in pale yellow (#FFFFC0)

$html = Set-CellColour -InputObject $html -Filter 'Handles -lt 100 -and Handles -gt 50' -Colour '#ffffc0'

Several colour filters can be applied as shown in the code below.

 

Code

The following code will produce something similar, and will get you on your way, you can also download it below…

# Need to set some variables first....
[string]$dt1    = (Get-Date -Format 'yyyy/MM/dd HH:mm')
[string]$un     = [System.Security.Principal.WindowsIdentity]::GetCurrent().Name.ToLower()
[string]$server = '[server/report name here]'
[string]$report = '[report name here]'




# Vaguely based on 
# https://community.spiceworks.com/scripts/show/2450-change-cell-color-in-html-table-with-powershell-set-cellcolor
Function Set-CellColour
{
    Param ( [Object[]]$InputObject, [string]$Filter, [string]$Colour, [switch]$Row )
    Begin
    {
        $Property = ($Filter.Split(' ')[0])
        If ($Filter.ToUpper().IndexOf($Property.ToUpper()) -ge 0)
        {
            $Filter = $Filter.ToUpper().Replace($Property.ToUpper(), '$value')
            Try { [scriptblock]$Filter = [scriptblock]::Create($Filter) } Catch { Exit }
        } Else { Exit }
    }

    Process
    {
        ForEach ($input In $InputObject)
        {
            [string]$line = $input
            If ($line.IndexOf('<tr><th') -ge 0)
            {
                [int]$index = 0
                [int]$count = 0
                $search = $line | Select-String -Pattern '<th>(.*?)</th>' -AllMatches
                ForEach ($match in $search.Matches)
                {
                    If ($match.Groups[1].Value -eq $Property) { $index = $count }
                    $count++
                }
                If ($index -eq $search.Matches.Count) { $index = -99; Break }
            }

            If ($line -match '<tr><td')
            {
                $line = $line.Replace('<td></td>','<td> </td>')
                $search = $line | Select-String -Pattern '<td(.*?)</td>' -AllMatches
                If (($search -ne $null) -and ($search.Matches.Count -ne 0) -and ($index -ne -99))
                {
                    $value = ($search.Matches[$index].Groups[1].Value).Split('>')[1] -as [double]
                    If ($value -eq $null) { $value = ($search.Matches[$index].Groups[1].Value).Split('>')[1] }
                    If (Invoke-Command $Filter)
                    {
                        If ($Row -eq $true) { $line = $line.Replace('<td>', ('<td style="background:{0};">' -f $Colour)) }
                        Else {
                            [string[]]$arr = $line.Replace('><','>■<').Split('■')
                            If ($arr[$index + 1].StartsWith('<td'))
                            {
                                $arr[$index + 1] = $arr[$index + 1].Replace($search.Matches[$index].Value, ('<td style="background:{0};">{1}</td>' -f $Colour, $value))
                                $line = [string]::Join('', $arr)
                            }
                        }
                    }
                }
            }
            Write-Output $line
        }
    }

    End
    { }
}


# CSS for the output table...
[string]$css = @'
<style>
    html body       { font-family: Verdana, Geneva, sans-serif; font-size: 12px; height: 100%; margin: 0; overflow: auto; }
    #header         { background: #0066a1; color: #ffffff; width: 100% }
    #headerTop      { padding: 10px; }
    .logo1          { float: left;  font-size: 25px; font-weight: bold; padding: 0 7px 0 0; }
    .logo2          { float: left;  font-size: 25px; }
    .logo3          { float: right; font-size: 12px; text-align: right; }
    .headerRow1     { background: #66a3c7; height: 5px; }
    .serverRow      { background: #000000; color: #ffffff; font-size: 32px; padding: 10px; text-align: center; text-transform: uppercase; }
    .sectionRow     { background: #0066a1; color: #ffffff; font-size: 13px; padding: 1px 5px!important; font-weight: bold; height: 15px!important; }
    table           { background: #eaebec; border: #cccccc 1px solid; border-collapse: collapse; margin: 0; width: 100%; }
    table th        { background: #ededed; border-top: 1px solid #fafafa; border-bottom: 1px solid #e0e0e0; border-left: 1px solid #e0e0e0; height: 45px; min-width: 55px; padding: 0px 15px; text-transform: capitalize; }
    table tr        { text-align: center; }
    table td        { background: #fafafa; border-top: 1px solid #ffffff; border-bottom: 1px solid #e0e0e0; border-left: 1px solid #e0e0e0; height: 55px; min-width: 55px; padding: 0px 10px; }
    table td:first-child   { min-width: 175px; text-align: left; }
    table tr:last-child td { border-bottom: 0; }
    table tr:hover td      { background: #f2f2f2; }
    table tr:hover td.sectionRow { background: #0066a1; }
</style>
'@

# Page header rows...
[string]$body = @"
<div id="header"> 
    <div id="headerTop">
        <div class="logo1">ACME</div>
        <div class="logo2">$report</div>
        <div class="logo3">&nbsp;<br/>Generated by $un on $dt1</div>
        <div style="clear:both;"></div>
    </div>
    <div style="clear:both;"></div>
</div>
<div class="headerRow1"></div>
<div class="serverRow">$server</div>
<div class="headerRow1"></div>
"@


# Get a list of processes, and convert to HTML... 
[string[]]$html = Get-Process | Select Name, CPU, Handles, Path, Company, FileVersion | Sort Name | ConvertTo-Html -Head $css -Body $body

# EXAMPLES
# Colour some  cells depending on the filters, filters can contain any valid forumla...
$html = Set-CellColour -InputObject $html -Filter 'Handles -lt 100 -and Handles -gt 50' -Colour '#ffffc0'
$html = Set-CellColour -InputObject $html -Filter 'Handles -gt  99'                     -Colour '#ffc0c0'
$html = Set-CellColour -InputObject $html -Filter 'Handles -lt  51'                     -Colour '#c0ffc0'
$html = Set-CellColour -InputObject $html -Filter 'Name    -eq "ccmexec"'               -Colour 'Gray' -Row
$html = Set-CellColour -InputObject $html -Filter 'Name    -eq "chrome"'                -Colour '#c0c0ff'

# Output the entire HTML to a text file...
$html += '<table><tr><td class="sectionRow">&nbsp;</td></tr></table>'
$html | Out-File .\Out-FancyHTML_Result.html

 

Have a play, see what you think.  Either leave a comment below, or on the Reddit thread here.

 

Download

Out-FancyHTM.7z

Win32_Product

Introduction

One of the easiest ways to get a list of installed applications on a machine is to use the WMI class Win32_Product.  This enumerates every application.  For example, the following code will display a table with the name of the installed application and it’s version number:

Get-WmiObject -Class Win32_Product | Select Name, Version

Great, now let’s filter it to find a specific application.  I’ll search for any application I have installed with ‘zip’ in the name:

Get-WmiObject -Class Win32_Product -Filter { Name like "%zip%" } | Select Name, Version

Name                                       Version
----                                       -------
7-Zip 9.38 (x64 edition)                   9.38.00.0

Just what we wanted, but there is a problem.

Problem

If you now look at your Application event log, you will notice a huge list of entries that say something like

Windows Installer reconfigured the product. Product Name: [name]. Product Version: [version]. Product Language: 1033. Manufacturer: [manufacturer]. Reconfiguration success or error status: 0.

You will see one entry for every single application you have installed (and a few more).  You may also notice that the command above took quite a long time to run.  In some cases I have seen it take up to 5 minutes to return just one entry.

This is a well known and blogged about topic, Microsoft even has a Knowledge Base article on it: KB974524.

 

Solution

My solution to this problem is a PowerShell function that searches the registry for the same information.  This has the benefit of being incredibly fast.

It works by searching the two locations where applications put their uninstall details.

HKLM\Software\Microsoft\Windows\CurrentVersion\Uninstall

HKLM\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall

The first is for 32-bit applications, the second for 64-bit applications.

To use my function simply call it with the following command.  Again, searching for any application with ‘zip’ in the name:

Win32_Product -serverName $env:computername -displayName 'zip'

This will return just the version number : 9.38.00.0

My function will only return the version number of the first application it finds that matches the input value.  You could expand on this and return all application names and version numbers using a hashtable or an array.  I’ll leave that up to you.

 

$regSearch = 'Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
Function Win32_Product
{
    Param ([string]$serverName, [string]$displayName)
    Try
    {
        $reg = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey('LocalMachine', $serverName)
        $regKey = $reg.OpenSubKey($regSearch)
        If ($regKey) { [array]$keyVal = $regKey.GetSubKeyNames() }
    }
    Catch { Return $null }

    $found = $false
    If (($regKey) -and ($keyVal.Count -gt 0)) {
        ForEach ($app In $keyVal) {
            $appKey = $regKey.OpenSubKey($app).GetValue('DisplayName')
            If ($appKey -like ('*' + $displayName + '*')) {
                $found = $true
                [string]$verCheck = $regKey.OpenSubKey($app).GetValue('DisplayVersion')
                If (-not $verCheck) { $verCheck = '0.1' } }
        }
        If ($found -eq $false) {
            If ($regSearch -like '*Wow6432Node*') {
                $regSearch = $regSearch.Replace('Wow6432Node', '')
                $verCheck = Win32_Product -serverName $serverName -displayName $displayName
            }
            Else { $verCheck = $null } }
    }
    Else { $verCheck = $null }
    $regKey.Close()
    $reg.Close()
    Return $verCheck
}

 

‘New’ Item Cleaner

Introduction

The ‘New >‘ item on the right-click menu is quite handy.  It can list a huge amount of document types for you to create new documents.  Most of the time these work, other times they don’t.

 

Problem

As this list grows when new applications are installed, it rarely shrinks when an application is removed.  This can lead to dead links.

Another issue with this list is custom desktops for Citrix XenApp users, or other users on locked down, permissions based terminal servers.  There may be links in the list that a particular user doesn’t have access to.

nic-1

Solution

One solution is to just ignore it.  Users may log tickets sayinIg they can’t access an application they are not allowed to have, causing you to waste time and effort on chasing your tails.

While I was working with a particular Citrix XenApp install quite a few years ago, I created a script in KIX that would remove the entire list of applications, except for “Text Document”  I have recently updated this script for PowerShell and thought that I would share it with you.

nic-2

 As you can see from the screen shots, all the new document links have gone except for the only one we want.

 

The Script

The script is listed below…

Push-Location
Set-Location 'REGISTRY::HKEY_CLASSES_ROOT'
# Get all keys under the root branch...
$RegKeyItems = Get-ChildItem
ForEach ($RegKey in $RegKeyItems)
{
    # Check if it starts with a full stop "."
    If ($RegKey.PSChildName.StartsWith('.'))
    {
        # Get all keys under current branch...
        $RegSubKeyItems = Get-ChildItem -Path $RegKey.PSChildName
        ForEach ($RegSubKey in $RegSubKeyItems)
        {
            # Looking for "ShellNew"...
            If ($RegSubKey.PSChildName -eq 'ShellNew')
            {
                # Make sure not to remove Shortcut Links or Text Documents
                If (($RegKey.PSChildName -ne '.lnk') -and ($RegKey.PSChildName -ne '.txt'))
                {
                    # Display and delete key...
                    write-host "Deleting: $RegSubKey"
                    Remove-Item -Path \$RegSubKey -Recurse -ErrorAction SilentlyContinue
                }
                # Same as above, checking one level deeper...
                $RegSubSubKeyItems = Get-ChildItem -Path $RegSubKey.PSChildName -ErrorAction SilentlyContinue
                ForEach ($RegSubSubKey in $RegSubSubKeyItems)
                {
                    # Looking for "ShellNew"...
                    If ($RegSubSubKey.PSChildName -eq 'ShellNew')
                    {
                        # Make sure not to remove Shortcut Links or Text Documents
                        If (($RegSubKey.PSChildName -ne '.lnk') -and ($RegSubKey.PSChildName -ne '.txt'))
                        {
                            # Display and delete key...
                            write-host "Deleting: $RegSubSubKey"
                            Remove-Item -Path \$RegSubSubKey -Recurse -ErrorAction SilentlyContinue
                        }
                    }
                }
            }
        }
    }
}
# Also remove that annoying Briefcase link...
Remove-Item -Path '\Briefcase\ShellNew' -Recurse -ErrorAction SilentlyContinue
Pop-Location

 

If you have any suggestions for improvements, or want to share your tweaks, leave a comment below.

 

Download

download-fileNew Item Cleaner.ps1

 

Passwords

Introduction

We all use passwords in our day-to-day lives.  Logging on to your favourite site, remoting into a server, connecting to your home-lab.  Passwords are here to stay.

There are people that think passwords will eventually disappear, but I don’t believe they will.  People still to be be authenticated in some way, whether it’s a typed password or a retinal scan.  It’s all the same.

While we still have typed passwords, you should at least make sure they are as secure as possible.

 

Passwords Tips

XKCD Password Strength (https://www.xkcd.com/936/)

XKCD Password Strength (https://www.xkcd.com/936/)

This is a list of tips that I try to use whenever I create a new password for a website or service…

  1. Use a random password generator,
  2. Use numbers and symbols if the service supports them,
  3. Make the password as long as possible.  The longer the better,
  4. Don’t tell anyone your passwords.
  5. Use a different password for each site or service.
    Never use the same password twice.

 

Password Managers

Now, I know there are many people out there that will have hundreds of websites and services that they use all the time, and trying to remember any password longer than 10 or 12 letters can be hard, so trying to remember more than an handful will be near impossible.

There is help though.  There are several tools that will help you store your passwords in an encrypted database and all you need to do is just remember one password.

One of the most popular free tools is called KeePass.  This will keep all your passwords safe and allow you to generate random passwords for your various websites.  There are others available, some free, some paid.  I personally use the paid tool 1Password, and have used this for quite a number of years.  However at work I do use KeePass quite a bit.

 

KeePass Setup

keepass-1Once you have downloaded and installed KeePass, run the application.  It will open to a blank form, click the File, New… to create a new password database.

 

This should be the last password that you will ever need to remember.  Make it a good one.  The longer and more complicated you can make it, the better.  As you can see, I used the password from the XKCD Comic above as an example.

Once you have entered a master password, click OK.

 

 

 

keepass-2

A new window will appear, enter some optional details for the title and description if you want to, and select the Security tab.  Here we should change the Key transformation number from the default of 6000 to something much larger.  Click the blue link labelled 1 second delay.  Depending on the speed of your computer, this number should change to something with a few more numbers.  Mine came out at about 16965888.

You can change the settings in the other tabs too if you like, but the defaults should be fine. Click OK when you are done.  Your new database will open and it will have some pre-configured folders and a couple of entries.

 

 

To create a new password entry, click Edit, Add Entry…  From the window that appears, fill in all the details you can.  A default password is created for you.  So if you are signing up for the first time to a site, you can use this suggested password.

keepass-4

keepass-passgenIf you don’t want this password, or the website doesn’t support the length or complexity, you can launch the password generator to create a new one.  Simple click the small key icon next to the password box.

keepass-5   keepass-6

 

Plug-ins

There are tons of plug-ins available for KeePass.  These perform all types of actions and allow you to expand the functionally KeePass.  There are plugins that will allow you to import an existing password database, or import your existing passwords from Firefox.  There are also plug-ins to hook into your favourite browser to help with auto-logging in.

Grab them from here.

Power Up Your HomeLab With PowerCLI

Introduction

As a follow on to the shut down home-lab script, I have also written a power up script for when power is restored to your home-lab and you want to get it up and running again quickly.  This script will assume your hosts are powered on and waiting, but nothing else has happened yet.

 

The Script

The script can be downloaded below, make sure you read though it fully and understand how it works.  I am not responsible for anything going wrong.!

At the top of the script, you will need to change the settings to match your own home-lab.

[string]$vCenter_Server                   = "svr-vc"
[string]$vCenter_Server_UserName          = "administrator"
[string]$vCenter_Server_Password          = "********"

[string]$Known_ESX_Host                   = "xxx.xxx.xxx.xxx"
[string]$Known_ESX_Host_UserName          = "root"
[string]$Known_ESX_Host_Password          = "********"

[string[]]$Critical_Servers_Startup_Order = ("live-dc","svr-dc","svr-sql", "svr-vc")

Lines

  1. The name of your virtual centre server
  2. The username you use to connect to your virtual centre server
  3. The password for the above username
  4. .
  5. The IP address of one of your ESXi hosts. This is explained below
  6. The username of the root account of for the above host
  7. The password for the root account
  8. .
  9. The servers in your critical folder, in the order they should be powered up

 

Starting from the known host (the same as in the shut down script), it will power up your critical servers in the order specified on line 9.  Once they are fully powered on (it checks that the VMware tools are available) it will check and wait for the VMware VirtualCenter Server service to be up and running.  Once running, the script will then start all the remaining virtual machines.

 

Download

Download the script from the link below, and again, make sure you test it before putting it live.!

download-fileStartUpEntireHomeLab.ps1

Shutdown Your HomeLab With PowerCLI

Introduction

If you run a large home-lab like I do, shutting it down in an emergency can be a slow process, especially if you are panicking about your UPS staying up long enough.  You  DO have a UPS don’t you.!?

I had written a PowerCLI script for myself that was custom to my specific lab.  I have since made it more generic so that everyone can use it if they wanted to.  The script does rely on one specific criteria for your home-lab though.

 

Home-Lab Layout

vc-foldersWithin the vCenter VMs and Templates view, you can organise your VMs into folders to help separate them into logical groups.  This has no bearing on the VMs themselves, its purely for your information.

As you can see by this image, I have a folder called Critical that I use to hold my important VMs.  These are…

  • live-dc : The DHCP and DNS server for my home network,
  • svr-dc : The domain controller, as well as DHCP and DNS for my private lab network,
  • svr-sql : The Microsoft SQL server for my private network.  This holds all the databases for my lab,
  • svr-vc : The VMware virtual centre server for my home-lab.

This Critical folder is important in my script, as it controls which VMs are not shut down immediately, but must wait until the end and be shut down in a specific order.

 

The Script

The script can be downloaded below, make sure you read though it fully and understand how it works.  I am not responsible for anything going wrong.!

At the top of the script, you will need to change the settings to match your own home-lab.

[string]$vCenter_Server                    = "svr-vc"
[string]$vCenter_Server_UserName           = "administrator"
[string]$vCenter_Server_Password           = "********"

[string]$Known_ESX_Host                    = "esx1"
[string]$Known_ESX_Host_UserName           = "root"
[string]$Known_ESX_Host_Password           = "********"

[string]$Critical_Folder                   = "Critical"
[string[]]$Critical_Servers_Shutdown_Order = ("svr-vc", "svr-sql", "svr-dc", "live-dc")

[int]$Shutdown_TimeOut = 180 # Seconds

Lines:

  1. The name of your virtual centre server
  2. The username you use to connect to your virtual centre server
  3. The password for the above username
  4. .
  5. The name of one of your ESXi hosts.  This is explained below
  6. The username of the root account of for the above host
  7. The password for the root account
  8. .
  9. The critical folder mentioned above (not case sensitive)
  10. The servers in your critical folder, in the order they should be shut down in
  11. .
  12. How long to wait for a clean shut down before killing power to a virtual machine

 

Specific ESXi Host Name

The reason for a specific host name in the script, is so that when you come to power on your home-lab, you will know exactly which host will hold your critical VMs.  The script will migrate all powered off VMs as well as your critical ones to this host before powering everything down.  If you have a number of hosts it’s helpful if you don’t need to check each one for your domain controller is virtual centre server.

 

Download

Download the script from the link below, and again, make sure you test it before putting it live.!

ShutdownEntireHomeLab.ps1download-file

More Pushover Configuration

Introduction

After successfully configuring Pushover with PRTG, I moved on to configuring my Synology DiskStation (easy) and VMware vCenter (harder) installation to also notify me via Pushover.

 

Register An Application

In both cases, I registered a new application within the Pushover console.  I did this so that each alert would come though with their own specific icon and description.  See my previous blog posting on how to do this.

I used the following icons for each application :

  • synology Synology
  • vmware VMware

 

Synology DiskStation Notifications

syno-notifications-1This is was the easiest change to make, simply…

  1. Login to your DiskStation,
  2. Open the Control Panel,
  3. Select the Notification option,
  4. Choose either (or both) of the following tabs…
    • Email
    • Push Service
  5. Enter your Pushover API email address
    • [user-key]+a=[api-key]@api.pushover.net

 

VMware vCenter Alerts

Setting up these alerts were a lot harder.  It took a lot of trial and error.  Hopefully this will save you the same hassle.

Before you start, make sure you have the Microsoft dotNet framework version 4 or later installed, as well as PowerShell 4 or later.  Next, we’ll create a folder and two files on your vCenter server…

  1. On the C: drive, create a folder called Scripts,
  2. Create two plain text files and enter the following code…

send-alert.bat

"c:\windows\system32\windowspowershell\v1.0\powershell.exe" c:\scripts\send-alert.ps1 -title '%1' -message '%2'

send-alert.ps1

param(
    [string]$title,
    [string]$message
)

$parameters = @{
    token    = "[api-key]"
    user     = "[user-key]"
    priority = "0"
    title    = $title
    message  = $message
}

$parameters | Invoke-RestMethod -Uri "https://api.pushover.net/1/messages.json" -Method Post

Remember to enter your own api-key and user-key into the .ps1 script file.

 

vmware-definitionsvCenter Configuration

Next we need to configure VMware vCenter to run the batch file whenever an alert condition is met.  At last count there are 68 different alert definitions within vCenter.  You could change each on manually, but that would take all day.  If you have VMware PowerCLI installed (and you really should have) you can script this.

Manual Steps…

  1. vmware-alarm-4Open VMware vSphere Client, and login to your vCenter server,
  2. Select the very top level entry in the tree view on the left,
  3. Choose the Alarms tab along the top,
  4. Click the Definitions button,
  5. The list of 68+ definitions are list.  Double-click on one of them.  I’ll choose Datastore usage on disk,
  6. Select the Actions tab,
  7. Click Add,
  8. From the new entry that appears,
    • change “Send a notification email
    • to “Run a command
  9. Enter the following code into the Configuration column…
c:\scripts\send-alert.bat "{targetName}  >  {eventDescription}" "{triggeringSummary}"
  1. Click OK
  2. Rinse and repeat for the remaining definitions, or…

Automatic Script…

  1. Open a PowerCLI command window, and connect to your vCenter server
  2. Copy and paste the following code, and let it run…
$al = Get-AlarmDefinition
ForEach ($a in $al) {
    Get-AlarmDefinition -Name $a | New-AlarmAction -Script -ScriptPath 'c:\scripts\send-alert.bat "{targetName}  >  {eventDescription}" "{triggeringSummary}"'
}

Make sure you get the single quotes ( ‘ ) and double-quotes ( ” ) correct

For a list of variables you can use, check the VMware documentation.

 

Results

Once you have alerts working, you should get alerts through looking something like this…

space-alert