PowerShell function for menu creation revisited

Sometimes when creating blog posts you just get excited to publish a post as soon as it’s finished, but then you realize you can do something better. So yesterday I published a post on how to use a function in PowerShell to create an interactive dynamic menu but in hindsight it could have been more “generic” – so today I’m back with a new post on how you can create that generic function by providing the query in a separate string instead as part of the function itself. This allows you to merely change the query (the input for creating the menu) without having to change anything inside the function. Now it’s a “real” function! ūüôā

I’ve added two mandatory parameters to the function, the first is the query to retrieve data and the second parameter is the task that you’d like to perform on the selected object. I’m calling the parameters -ListItem and -GetItem but the actual query is set in the $MyQuery and $MyTask string and they are then passed on to the function to perform the task. So if I’d like to get a list of VMs I just change the $MyQuery to something like “(Get-VM | Sort-Object)” and if I’d like to get a list of Datastores I set $MyQuery = “(Get-Datastore | Where {$_.Type -eq ‘VMFS’} | Sort-Object -Descending FreeSpaceGB)”. Once the list is presented and you select an object you then pass on what’s going to happen with it with the $MyTask – so if I want to retrieve a specific VM I set it to “Get-VM” but it could be anything (like Remove-VM but there’s no warning so be careful!)

Get a list of VMs
Get a list of VMFS datastores and sort them based on free space

Calling the function is then done using this syntax:
Set-Menu -ListItem $MyQuery -GetItem $MyTask

$VIServer = "vcsa.mydomain.com"
$VIUsername = "administrator@vsphere.local"
$VIPassword = "MyPassword"

Connect-VIServer $VIServer -User $VIUsername -Password $VIPassword -WarningAction SilentlyContinue

$MyQuery = (Get-Datastore | Where {$_.Type -eq 'VMFS'} |Sort-Object -Descending FreeSpaceGB)
$MyTask = "Get-Datastore"

Function Set-Menu {
    Param (
        [Parameter(Mandatory)]
        [ValidateNotNullOrEmpty()]
        $ListItem,

        [Parameter(Mandatory)]
        [ValidateNotNullOrEmpty()]
        $GetItem
    )

    Try {
    $menu = @{}
    For ($i=1;$i -le $ListItem.count; $i++)
        { Write-Host "$i. $($ListItem[$i-1].Name)"
        $menu.Add($i,($ListItem[$i-1].Name)) }
    [int]$ans = if(($ans = Read-Host "Select item or press enter to accept default value [1]") -eq '') {"1"} else {$ans}
    $Selection = $menu.Item($ans); Invoke-Expression $($GetItem +  " " + $Selection)
    }

    Catch {
    Write-Host "The selection you made is unavailable, please make a valid selection between 1 and $($i-1)..."
    }
}

cls

Set-Menu -ListItem $MyQuery -GetItem $MyTask

Disconnect-VIServer $VIServer -Confirm:$false
Set-Menu waiting for user input
Set-Menu has executed the task defined in $MyTask

The way you can use the function is up to you but I use it in William Lams vghetto-vsphere-automated-lab-deployment script

Simplify your life with dynamic menus in PowerShell

If you are anything like me you’re setting up test or demo environments and then tearing them down a few hours later when you’re done with what ever testing you were doing. While setting up a VMware vSphere test environment is super easy using powershell/powercli (if you haven’t already visited William Lams web page, I highly recommend it and while you’re there grab the PowerShell scripts to deploy vSphere/vSAN/NSX environments. Kudos to William for everything you do for the vCommunity!!), I use his powershell scripts almost on a daily basis!

Now while setting up a single lab environment is usually not a big problem using the scripts provided by Willam but when you start setting up multiple labs (I typically have like 3 or 4 labs set up on any given time for different purposes) you might run out of some resources statically configured in the script, for instance your datastore configured doesn’t have enough capacity. So why not enhance the experience with dynamic selection of for instance datastore (or in my example: DatastoreCluster). Turns out it’s really easy to build an interactive dynamic menu of your datastoresclusters and use the menu to select where to install the lab environment.

I’m using a function to call the datastoreclusters I can use. I have a basic error handling included that you might need to extend. The code below includes 2 examples, the first function is calling available DatastoreClusters from a vCenter server and ordering them based on available free space and the second example gets a list of port groups available on my distributed switch. I’ve also assigned a default selection/value for faster deployments, just press enter to select the default value.

$VIServer = "vcsa.mydomain.com"
$VIUsername = "administrator@vsphere.local"
$VIPassword = "MyPassword"

Connect-VIServer $VIServer -User $VIUsername -Password $VIPassword -WarningAction SilentlyContinue

Function Show-DatastoreClusters ($Title = 'My Datastores'){
Try {
$AvailDatastores = (Get-Datastore | Where {$_.Type -eq "VMFS"} |Sort-Object -Descending FreeSpaceGB)
$menuds = @{}
For ($i=1;$i -le $AvailDatastores.count; $i++)
{ Write-Host "$i. $($AvailDatastores[$i-1].Name) - $([math]::Round($AvailDatastores[$i-1].FreespaceGB,1)) GB free space"
$menuds.Add($i,($AvailDatastores[$i-1].Name)) }
[int]$ansds = if(($ansds = Read-Host "Select Datastore or press enter to accept default value [1]") -eq ''){"1"} else {$ansds}
$DatastoreSelection = $menuds.Item($ansds) ; Get-Datastore $DatastoreSelection
}
Catch { Write-Host "The selection you made is unavailable, please make a valid selection between 1 and $($i-1)..." Start-Sleep 2 }
}

Function Show-PortGroups ($Title = 'My Portgroups'){
Try {
$AvailPortgroups = (Get-VDSwitch -Name "DSwitch" | Get-VDPortgroup)
$menupg = @{}
For ($i=1;$i -le $AvailPortgroups.count; $i++)
{ Write-Host "$i. $($AvailPortgroups[$i-1].Name)"
$menupg.Add($i,($AvailPortgroups[$i-1].Name)) }
[int]$anspg = if(($anspg = Read-Host "Select portgroup or press enter to accept default value [7]") -eq ''){"7"}else{$anspg}
$PortgroupSelection = $menupg.Item($anspg) ; Get-VDPortgroup $PortgroupSelection
}
Catch { Write-Host "The selection you made is unavailable, please make a valid selection between 1 and $($i-1)..." Start-Sleep 2 }
}
cls
Write-Host `n
$VMDatastore = Show-DatastoreClusters
Write-Host `n
$VMNetwork = Show-Portgroups
Write-Host `n
$VMDatastore
$VMNetwork

Disconnect-VIServer $VIServer -Confirm:$false

The function above can then be included in the script from William Lam. When calling the function the output will look something like this (the menus being produced are highlighted in red):

Function to build dynamic menu

If you’d like you can add some extra granularity when enumerating the datastore, expand the “Where” statement to just get the datastores with a specific name:

{$_.Type -eq "VMFS" -and $_.Name -like "VMFS-ESXi7*"}

Once you got you script fetching the desired data you can add the function to any script, I’ve added it to William’s script for some added flexibility.

Veeam, PowerShell and SAN snapshot

So a week or so ago I wrote a post about using PowerShell to add snapshots to a SAN found in Veeam Backup & Replication. It was a quick test to see if it worked, now I’ve slightly improved the script.

The Get-HP4* cmdlets is specifically for HPE StoreVirtual VSA/P4000/LeftHand line. If you have another supported SAN Storage system use the correct cmdlets:

NetAPP Storage Systems
HPE 3Par StoreServ Storage Systems
HPE StoreVirtual Storage Systems
EMC VNX Storage Systems

You can accomplish the same thing using the management tool for the SAN, taking recurring snapshots. But in the case of HPE StoreVirtual it’s a licensed feature and it can only occur every 30 minutes so if you need it more often or you’re lacking the license you can use the PowerShell script instead.

Add-PSSnapin VeeamPSSnapIn -ErrorAction SilentlyContinue

$source_storage = ‘Veeam-VSA-MGMTG’
$source_cluster = ‘veeam-vsa-cluster’
$source_vol_name = ‘datastore1’
$snapshot_name = ‘Oh_snap_’+(Get-Date -Format MMddhhmm)

#Create a new snapshot
try {
‘Trying:’
$getvolume = (Get-HP4Storage -Name $source_storage | Get-HP4cluster -Name $source_cluster | Get-HP4Volume -name $source_vol_name)
‘getvolume-Name: ‘ + $getvolume.Name
‘getvolume-InternalId: ‘ + $getvolume.InternalId
‘getvolume-IsThin: ‘ + $getvolume.IsThinProvision
‘getvolume-Size: ‘ + $getvolume.Size

$getvolume | Add-HP4Snapshot -name $snapshot_name
} catch {
‘Failed to find storage, cluster or datastore’
‘Unable to create snapshot’
break
}

#Remove the oldest snapshot if more than 4 are available
$getsnapshot = ($getvolume | Get-HP4Snapshot | Where-Object {$_.Name -like ‘Oh_snap_*’})
$snapshot_count = @($getsnapshot).Count

if ($snapshot_count -ge 4) {
$getsnapshot | Sort-Object creationtimeutc | Select-Object -First 1 | Remove-HP4Snapshot
}

Populate your Veeam lab with PowerShell

If you, like me, have the need to constantly rebuild a lab environment where the servers are installed already but it lacks any configuration you probably realized that PowerShell is you friend. I have a lab environment that I tear down and build up again really often using templates in my VMware environment. In this environment I have all the infrastructure components installed but not configured in Veeam Backup & Replication so whenever I want to show-and-tell I first need to configure stuff. It might take a while to do, so why not automate with PowerShell?

The script below adds a few managed servers, adds backup proxies, creates a Scale-Out Backup Repository with 2 extents, adds 2 WAN accelerators. On top of that it adds a Tape proxy, connects to a HP VSA and takes a snapshot.

Add-PSSnapin VeeamPSSnapIn -ErrorAction SilentlyContinue

$Infra_Administrator = "Domain1\Administrator"
$Infra_Password = 'Password1'
$Lab_Administrator = "Domain2\Administrator"
$Lab_Password = 'Password2'
$ESXi_root = "root"
$ESXi_Password = 'Password3'
$Oracle_User = "oracle"
$Oracle_Password = 'Password4'
$HPE_User = 'HpeUser'
$HPE_Password = 'Password5'
$VBRserver = Get-VBRServer -Name "VEEAM-VBR.domain1.local"

Add-VBRCredentials -Type Windows -User $Infra_Administrator -Password $Infra_Password -Description $Infra_Administrator
Add-VBRCredentials -Type Windows -User $Lab_Administrator -Password $Lab_Password -Description $Lab_Administrator
Add-VBRCredentials -Type Linux -User $Oracle_User -Password $Oracle_Password -SshPort 23 -ElevateToRoot -AddToSudoers -RootPassword $Oracle_Password -Description "oracle"

#Add servers as ‚ÄĚmanaged servers‚ÄĚ
Add-VBRESXi ‚ÄďName "VEEAM-ESX" -user root -password 'Password3'
Add-VBRWinServer -Name "VEEAM-HYPERV" -credentials $Infra_Administrator
Add-VBRWinServer -Name "VEEAM-Remote" -credentials $Infra_Administrator
Add-VBRHvHost -Name "VEEAM-HYPERV" -credentials $Infra_Administrator

#Remove/Add proxy with 1 concurrent task limit
Get-VBRViProxy -Name "VMware Backup Proxy" | Remove-VBRViProxy -Confirm
Add-VBRViProxy -Server $VBRserver -Description "VMware Backup Proxy" -MaxTasks 1

#Add Backup Repositories and Scale-Out Backup Repository
Add-VBRBackupRepository -Server $VBRserver -Name "Remote Repository" -Folder "X:\Backups" -Type WinLocal -MaxConcurrentJobs 4 -Credentials $Infra_Administrator
Add-VBRBackupRepository -Name "Local Backup Repository" -Server $VBRserver -Folder "E:\Backups" -Type WinLocal -MountServer $VBRserver -VPowerNFSFolder "C:\ProgramData\Veeam\Backup\NfsDatastore" -MaxConcurrentJobs 4 -Credentials $Infra_Administrator
Set-VBRConfigurationBackupJob -Repository "Remote Repository"
Add-VBRScaleOutBackupRepository -Name "Main Backup Repository" ‚ÄďPolicyType DataLocality ‚ÄďExtent ‚ÄúDefault Backup Repository‚ÄĚ, ‚ÄúLocal Backup Repository‚ÄĚ

#Add WAN accelerators
Add-VBRWANAccelerator -Server "VEEAM-Remote" -Description "Remote WAN Accelerator" -CachePath "X:\VeeamWAN" -CacheSize 10 -CacheSizeUnit GB
Get-VBRLocalhost | Add-VBRWANAccelerator -Description "Local WAN Accelerator" -CachePath "X:\VeeamWAN" -CacheSize 10 -CacheSizeUnit GB

#Add a Virtual Lab and Application group
$VLABhost = Get-VBRServer -Type ESXi
$VLABdatastore = Find-VBRViDatastore -Name "datastore1" -Server $VLABhost
Add-VSBVirtualLab -Name "VEEAM-ESX VLAB1" -Server $VLABhost -Datastore $VLABdatastore

Find-VBRViEntity -Name "VEEAM-DC01", "VEEAM-EX01" | Add-VSBViApplicationGroup -Name "Exchange"

#Add SAN and Tape and make a snapshot on the SAN
Add-HP4Storage -DnsOrIpAddress "10.20.30.40" -User $HPE_User -Password $HPE_Password -Description "HPE Storage"
Get-VBRServer -Name "VEEAM-Remote" | Add-VBRTapeServer
Get-HP4Volume -name "datastore1" | Add-HP4Snapshot -name "datastore1_SS_1"

Veeam and PowerShell: A perfect match!

I read a really good and useful blog post a while ago from Preben Berg from Veeam describing how to use PowerShell to restore a database from backup to a dev environment. This made me think on another scenario that would be fun to script that might come in handy someday.

What if you have a SAN that you would want do snapshots on once an hour and save some of those historical snapshots rotating the oldest one.

Now for the disclaimer part, this is merely meant to showcase how you might accomplish this.¬†There are no safety features built in. Do not use it in production and use it at your¬†own risk. However if you’d like to do some further testing of your own, you can download a virtual SAN from HP – free of charge up to 1 TB. Nice!

First things first. We need to create a PowerShell script.

Let’s define which volume to use:
$source_vol_name = "datastore1"

Then we add a naming convention to use for the snapshots with a timestamp at the end:
$snapshot_name = "Oh_snap_"+(Get-Date -Format MMddhhmm)

Let’s count how many snapshots exist on the volume:
$snapshot_count = @(Get-HP4Volume -name $source_vol_name | Get-HP4Snapshot).Count

Now let’s create a snapshot:
$snapshot_create_session = Get-HP4Volume -name $source_vol_name | Add-HP4Snapshot -name $snapshot_name -description "Automated snapshot"

I would like to save the 4 latest snapshots on the volume and delete the oldest:
if ($snapshot_count -ge 4 {
$snapshot_remove_session = Get-HP4Volume -name $source_vol_name | Get-HP4Snapshot | Sort-Object creationtimeutc | Select-Object -First 1 | Remove-HP4Snapshot
}

If I would leave out “Get-HP4Volume -name $source_vol_name” ¬†in the above statement then I would delete to oldest snapshot available on any of the volumes (not just datastore1) which is not our intent.

Let’s put it all together:

Add-PSSnapin VeeamPSSnapIn -ErrorAction SilentlyContinue

$source_vol_name = “datastore1”
$snapshot_name = “Oh_snap_”+(Get-Date -Format MMddhhmm)
$snapshot_count = @(Get-HP4Volume -name $source_vol_name | Get-HP4Snapshot).Count

$snapshot_create_session = Get-HP4Volume -name $source_vol_name | Add-HP4Snapshot -name $snapshot_name -description “Automated snapshot”

if ($snapshot_count -ge 4 {
$snapshot_remove_session = Get-HP4Volume -name $source_vol_name | Get-HP4Snapshot | Sort-Object creationtimeutc | Select-Object -First 1 | Remove-HP4Snapshot
}

That’s all, just a few lines of code and we can accomplish cool things.

Then all we have to do is add the script as a scheduled task on the VBR server and run¬†it once an hour or what ever intervall we’d like.

But let’s see how the infrastructure looks prior to running the script:

1. SAN datastores

 

Open Task Scheduler and create a new task:

2. Create scheduled task

 

Let’s configure it to run once an hour starting at 10 PM:

3. Run at 1 hour intervall

 

We add and action, starting the PowerShell script:

4. Add PowerShell script to run

 

Looks ok once it’s added:

5. Scheduled task has been added

 

First time the script ran a snapshot was created as expected:

6. First run of the script

 

(I changed the script to run every 5 minutes to speed up the process), now we have 4 snapshots:

7. 4 initial snaps

 

When the fifth snapshot has been taken the oldest snapshot is deleted (the snapshot created at 10:07 PM in the previous picture):

8. 4 snaps rolling