Node-Red flow for blinds based on sun position

This is my node-red flow for automating my blinds based on the position of the sun. My home office is south facing, so if you copy this you will need to adjust the Azimuth angles for the direction you window is facing.

The automation starts as soon as the rises above the horizon. The first function saves the current azimuth so it can be refered to later on in the flow and checks to see if it has hit the critical point just before the sun starts to shine directly into the room. Based on the logic, if the sun has passed 165 degrees, it will wait for 5 minutes then get the current azimuth. The next function will check to see if the previously stored azimuth and the latest azimuth are now 5 degrees apart. If not it will wait another 5 minutes, then test again.

Once more than 5 degrees of motion has been detected, the flow will get the current position of the blind. If the blind is still open, the last function subtracts 10 from the blinds position (0 is fully extended 100 fully open) and then passes that to the blind. Once the blind is fully closed, the process stops. Once the sun is beyond 280 degrees, the process stops.

Code in the functions are below. Note that I am not a coder so this is probably terrible:

Save current azimuth:
var azimuth =;
if (azimuth < "165") {
msg.azimuth = "0";

msg.switch = "reset";
} else {
msg.azimuth = azimuth;
msg.switch = "continue";
return msg;

Calculate the azimuth change:
var enow =;
var eold = msg.azimuth;
var result = (enow - eold);

if (result >= "5" && enow < "280") {
msg.payload = "true";
} else if (enow > "280") {
msg.payload = "end";
} else {
msg.payload = "false";
msg.result = result;
return msg;

Update position:
var current =;
if (current > 0) {
msg.newPosition = (current - 10)
} else {
msg.newPosition = 0
newMsg = {
payload: {
"data": {
"position": msg.newPosition
return newMsg;

Controlling TP-Link switch using a Harmony remote

This code goes into configuration.yaml
# Hue Emulation to allow Logitech remote to control TP-Link switches
expose_by_default: false
- switch
- light
name: "Small Lounge Lamp HA"
hidden: false
name: "Christmas Tree HA"
hidden: false
name: "Living Room 1 HA"
hidden: false
name: "Living Room 2 HA"
hidden: false
name: "Lounge Lamp 1 HA"
hidden: false
name: "Lounge Lamp 2 HA"
hidden: false

Then add Hue devices the alexa app; this is why I have named them as HA, so I know they are from the emulated Hue. Open the Logitech Harmony software and they should appear in the devices:

You can then create groups of lights to control using the buttons on the remote.

Add IP from hostname to Exchange Receive Connector

<# .SYNOPSIS Adds the IP address of a server to the SMTP relay .DESCRIPTION Gets the IP address of a list of servers and adds them to the Receive connector on the UK CAS servers. .USAGE The input CSV should have one server per line, no header and use the FQDN. .NOTES Version: 1.1 Authors: Jon Hadden Script: Add-IP-to-SMTP-Relay.ps1 Creation date: 06-02-2018 2018-02-06: 1.0 - Inital Script Creation 2018-02-08: 1.1 - Updated logging to record Computer name, IP address and any errors generated #>

<# .VARIABLES These variables are the only ones that may require editing. #>
$CASServer01 = "CAS-Server-01\Receive Connector"
$CASServer02 = "CAS-Server-02\Receive Connector"
$CASServer03 = "CAS-Server-03\Receive Connector"
$CASServer04 = "CAS-Server-04\Receive Connector"
$CASServer05 = "CAS-Server-05\Receive Connector"
$CASServer06 = "CAS-Server-06\Receive Connector"
$Domain1 = ""
$Domain2 = ""
$Domain3 = ""

<# .FUNCTIONS Define the functions for later use in the script. #>

# Create the function for getting the csv file
Function Fn-Get-FileName($initialDirectory)
[System.Reflection.Assembly]::LoadWithPartialName("") | Out-Null
$OpenFileDialog = New-Object System.Windows.Forms.OpenFileDialog
$OpenFileDialog.initialDirectory = $initialDirectory
$OpenFileDialog.filter = "CSV (*.csv)| *.csv"
$OpenFileDialog.ShowDialog() | Out-Null

# Create a function for getting the error and writing to log
Function Fn-Get-Error
if ($Error -ne $null)
$ErrMsg = $Error[0].Exception.Message
$ErrTgt = $Error[0].CategoryInfo.TargetName
Write-Log "Error:$ErrMsg : $ErrTgt"

# Function for writing to a log file
Function Write-Log {
Time = (Get-Date -f g)
Message = $Message
} | Export-Csv -Path "C:\Temp\$((Get-Date).ToString('yyyy-MM-dd'))_SMTP_Relay.log" -Append -NoTypeInformation

<# .SCRIPT Now start the script #>

# Check if C:\Temp exists and create if not
if (-not (Test-Path "C:\Temp"))
New-Item -ItemType directory -Path C:\Temp

# Import the csv
$path = Fn-Get-Filename
$ComputerList = Get-Content $path

# Initiate the Exchange 2010 tools & exit if not found
if (Test-Path $env:ExchangeInstallPath\bin\RemoteExchange.ps1)
Write-Host "Loading Exchange Management Tools."
Add-PSSnapin Microsoft.Exchange.Management.PowerShell.E2010;
. $env:ExchangeInstallPath\bin\RemoteExchange.ps1
Write-Host "Exchange Management Tools required. Please install the tools."

# Build an array with the Computer name and IP address
$SMTPAddresses = @()
foreach ($ComputerName in $ComputerList)
# Check if the FQDN is correct in the input file
if ($ComputerName -match $Domain1 -or $ComputerName -match $Domain2 -or $ComputerName -match $Domain3)
# Get the IP address from the Test-Connection result
$IPInfo = Test-Connection "$ComputerName" -count 1 | select @{Name="Computername";Expression={$_.Address}},Ipv4Address
$SMTPAddresses += $IPInfo
# If the FQDN is not found, exit the script
Write-Host "FQDN not found. Please use the FQDN in the input file."

# Get the Receive Connector information
$CAS01 = Get-ReceiveConnector $CASServer01
$CAS02 = Get-ReceiveConnector $CASServer02
$CAS03 = Get-ReceiveConnector $CASServer03
$CAS04 = Get-ReceiveConnector $CASServer04
$CAS05 = Get-ReceiveConnector $CASServer05
$CAS06 = Get-ReceiveConnector $CASServer06

# Get each IP and add it to the Receive connector on each Exchange CAS server
foreach ($Computer in $SMTPAddresses)
if ($CAS01.RemoteIPRanges -notcontains $($Computer.Ipv4Address.IPAddressToString))
Write-Log "Adding $($Computer.Computername) - $($Computer.Ipv4Address.IPAddressToString) to $CASServer01"
$CAS01.RemoteIPRanges += "$($Computer.Ipv4Address.IPAddressToString)"
Set-ReceiveConnector $CASServer01 -RemoteIPRanges $CAS01.RemoteIPRanges -ErrorAction SilentlyContinue
Write-Log "$($Computer.Computername) - $($Computer.Ipv4Address.IPAddressToString) already exists on $CASServer01"
if ($CAS02.RemoteIPRanges -notcontains $($Computer.Ipv4Address.IPAddressToString))
Write-Log "Adding $($Computer.Computername) - $($Computer.Ipv4Address.IPAddressToString) to $CASServer02"
$CAS02.RemoteIPRanges += "$($Computer.Ipv4Address.IPAddressToString)"
Set-ReceiveConnector $CASServer02 -RemoteIPRanges $CAS02.RemoteIPRanges -ErrorAction SilentlyContinue
Write-Log "$($Computer.Computername) - $($Computer.Ipv4Address.IPAddressToString) already exists on $CASServer02"
if ($CAS03.RemoteIPRanges -notcontains $($Computer.Ipv4Address.IPAddressToString))
Write-Log "Adding $($Computer.Computername) - $($Computer.Ipv4Address.IPAddressToString) to $CASServer03"
$CAS03.RemoteIPRanges += "$($Computer.Ipv4Address.IPAddressToString)"
Set-ReceiveConnector $CASServer03 -RemoteIPRanges $CAS03.RemoteIPRanges -ErrorAction SilentlyContinue
Write-Log "$($Computer.Computername) - $($Computer.Ipv4Address.IPAddressToString) already exists on $CASServer03"
if ($CAS04.RemoteIPRanges -notcontains $($Computer.Ipv4Address.IPAddressToString))
Write-Log "Adding $($Computer.Computername) - $($Computer.Ipv4Address.IPAddressToString) to $CASServer04"
$CAS04.RemoteIPRanges += "$($Computer.Ipv4Address.IPAddressToString)"
Set-ReceiveConnector $CASServer04 -RemoteIPRanges $CAS04.RemoteIPRanges -ErrorAction SilentlyContinue
Write-Log "$($Computer.Computername) - $($Computer.Ipv4Address.IPAddressToString) already exists on $CASServer04"
if ($CAS05.RemoteIPRanges -notcontains $($Computer.Ipv4Address.IPAddressToString))
Write-Log "Adding $($Computer.Computername) - $($Computer.Ipv4Address.IPAddressToString) to $CASServer05"
$CAS05.RemoteIPRanges += "$($Computer.Ipv4Address.IPAddressToString)"
Set-ReceiveConnector $CASServer05 -RemoteIPRanges $CAS05.RemoteIPRanges -ErrorAction SilentlyContinue
Write-Log "$($Computer.Computername) - $($Computer.Ipv4Address.IPAddressToString) already exists on $CASServer05"
if ($CAS06.RemoteIPRanges -notcontains $($Computer.Ipv4Address.IPAddressToString))
Write-Log "Adding $($Computer.Computername) - $($Computer.Ipv4Address.IPAddressToString) to $CASServer06"
$CAS06.RemoteIPRanges += "$($Computer.Ipv4Address.IPAddressToString)"
Set-ReceiveConnector $CASServer06 -RemoteIPRanges $CAS06.RemoteIPRanges -ErrorAction SilentlyContinue
Write-Log "$($Computer.Computername) - $($Computer.Ipv4Address.IPAddressToString) already exists on $CASServer06"

Powershell script to get all group membership including nested groups

### Powershell script to get all users group membership including nested groups ###
# Load AD Module
Import-Module ActiveDirectory

# Define function for group lookups
function GetGroups ($object)
Get-ADPrincipalGroupMembership $object | ForEach `
Get-ADPrincipalGroupMembership $_
# Set query to ask for username
$username = Read-Host -Prompt ‘Enter the username’

# Ask for a username to check & export results to CSV file
GetGroups $username | select name,GroupScope,GroupCategory -Unique | Export-CSV C:\Scripts\Reports\$username-All_Groups.csv

The transaction log for database ‘VIM_VCDB’ is full. To find out why space in the log cannot be reused, see the log_reuse_wait_desc column in sys.databases

Recently I have gotten this error on a couple of vCenter servers I manage, due to them using SQL Express installs for the VCDB and then having a large number of deletes occur on events & tasks. The reason for the message is that by default the SQL express DB log file is limited to 500MB and the deletes fill the log up. Now all the “fixes” I’ve found by searching for the error in the title simply say “change the limit” so it doesn’t fill up. Which I don’t really like as a fix as it’s a workaround, especially as the log file on one of the servers had to grow to around 15GB, which is insane for a 2GB DB.

Instead I started looking for a way to get this back under control and in the end I had to search for the database being full, which lead me to

Now most of the article was irrelevant to me as I had already performed the tasks purging the DB of old events. However, there is a command at the end of the article:

dbcc shrinkdatabase ( VIM VCDB , 5 )

Now I’m not a SQL admin so I don’t know what sort of affects this might have on the DB (negative or otherwise) but after backing up the DB I ran the command and the DB & Log file both shrank and haven’t grown massively since.

Upgrading ESXi 5.0 host to 5.5

Over the past week I have been trying to upgrade an ESXi 5.0 host to ESXi 5.5 Update 3d. Trying to upgrade it via VUM I would get “Cannot execute upgrade script on host”. Trying to patch the host to the latest 5.0 patches via VUM would generate the “The host returns esxupdate error code:15. The package manager transaction is not successful. Check the Update Manager log files and esxupdate log files for more details.” message.

Checking esxupdate.log, the only error I could find was:

ERROR: An esxupdate error exception was caught:
ERROR: Traceback (most recent call last):
ERROR: File “/usr/sbin/esxupdate”, line 216, in main
ERROR: cmd.Run()
ERROR: File “/build/mts/release/bora-768111/bora/build/esx/release/python-2.6-lib-zip-stage/768111/visor/pylib/python2.6/site-packages/vmware/esx5update/”, line 144, in Run
ERROR: File “/build/mts/release/bora-768111/bora/build/esx/release/python-2.6-lib-zip-stage/768111/visor/pylib/python2.6/site-packages/vmware/esximage/”, line 243, in InstallVibsFromSources
ERROR: File “/build/mts/release/bora-768111/bora/build/esx/release/python-2.6-lib-zip-stage/768111/visor/pylib/python2.6/site-packages/vmware/esximage/”, line 345, in _installVibs
ERROR: File “/build/mts/release/bora-768111/bora/build/esx/release/python-2.6-lib-zip-stage/768111/visor/pylib/python2.6/site-packages/vmware/esximage/”, line 388, in _validateAndInstallProfile
ERROR: File “/build/mts/release/bora-768111/bora/build/esx/release/python-2.6-lib-zip-stage/768111/visor/pylib/python2.6/site-packages/vmware/esximage/”, line 630, in Stage
ERROR: File “/build/mts/release/bora-768111/bora/build/esx/release/python-2.6-lib-zip-stage/768111/visor/pylib/python2.6/site-packages/vmware/esximage/”, line 463, in _download_and_stage
ERROR: InstallationError: (‘VMware_locker_tools-light_5.0.0-3.90.3982828’, ‘[Errno 32] Broken pipe’)

In VUA.log, the only errors were related to PACKAGE_COMPLIANCE indicating that the 5.5.0 packages weren’t available (as it’s a 5.0.0 host).

So searching around a bit, I found this article:

I tried all the solutions offered in the article, but none worked. Checking the disk space with the vdf -h command showed that there was ample free space on all partitions, as did running df -h. What I had noticed was that the /locker/packages/5.0.0/ folder was missing so I recreated it and attempted to re-run the patching, which would fail. I ended up creating a VMware Workstation image of an ESXi 5.0 host so that I could grab the 5.0.0 folder from it to copy onto the existing host. However it was at this stage, when trying to copy 5.0.0 tools back onto the host using WinSCP, I’d get an error 4. This lead me to again look into the free space on the drive, which I did by logging onto the host via SSH and running:

df -h

This showed that the volume Hypervisor3 was 100% full!

Filesystem   Size    Used Available Use% Mounted on
vfat       285.9M  285.9M     16.0k  100% /vmfs/volumes/Hypervisor3

Investigating the Hypervisor3 store, there were a number of very old files, amounting to around 100MB in the /vmfs/volumes/Hypervisor3/var/core/ folder. I copied these to my local machine, deleted from the host and re-copied the tools which then completed successfully.

After clearing the space I was still unable to upgrade the host to 5.5, but I was able to remidate with the latest 5.0 patches. Once the host was up to date on the patch level, re-running the upgrade to 5.5 was successful.

Lesson learned from this experience:
Check that there are no old dump files before upgrading a host!

Updated inactive computer accounts script

Original found on Spiceworks forum. Now exports to CSV and replaces computer description with date that script is run.

# This PowerShell Command will query Active Directory and return the computer accounts which have not logged for the past
# 60 days.  You can easily change the number of days from 60 to any number of your choosing.  lastLogonDate is a Human
# Readable conversion of the lastLogonTimeStamp (as far as I am able to discern.  More details about the timestamp can
# be found at technet –  –MWT, 03/12/13

# Activates the required module in PS for this script to work.
import-module activedirectory

# 30 is the number of days from today since the last logon.
$then = (Get-Date).AddDays(-30)

# Generate the CSV for those comuters affected
Get-ADComputer -Property Name,lastLogonDate -Filter {lastLogonDate -lt $then} | where-object {$_.DistinguishedName -notlike “*OU To Exclude*”} | Select-Object -property Name,lastLogonDate | sort Name,lastLogonDate | Export-Csv C:\SCRIPTS\Logs\Disabled_Computer_Records-$((Get-Date).ToString(‘yyyy-MM-dd’)).csv -Delimiter “;” -notype

# If you would like to Disable these computer accounts, uncomment the following line:
Get-ADComputer -Property Name,lastLogonDate -Filter {lastLogonDate -lt $then} | where-object {$_.DistinguishedName -notlike “*OU To Exclude*”} | Set-ADComputer -Enabled $false -Description “$($_.Description) Disabled by script on $(get-date -format yyyy-MM-dd)”

# If you would like to Remove these computer accounts, uncomment the following line:
# Get-ADComputer -Property Name,lastLogonDate -Filter {lastLogonDate -lt $then} | Remove-ADComputer

Finding old AD user accounts with powershell

The following is a modified script that I have used to pulling old/inactive user accounts out of AD.

# Activate the required module in PS for this script to work.
import-module activedirectory

# 90 is the number of days from today since the last logon.
$then = (Get-Date).AddDays(-90)

# -Searchbase is OU where user accounts are kept. *Excluded Sub OU* is required if a sub OU Needs to be excluded from the search.
Get-ADUser -Property Name,lastLogonDate -Filter {Enabled -eq $true -and lastLogonDate -lt  $then} -SearchBase “OU=AD Users,DC=Domain,DC=com” | ? {($_.DistinguishedName -notlike “*Excluded Sub OU*”)} | Select-Object -property Name,lastLogonDate | sort Name,lastLogonDate | Export-Csv C:\SCRIPTS\Logs\User_Records.csv -Delimiter “;” -notype

TeamViewer on Converted VM

I installed an SSD into my home ESXi host and wanted to migrate my Windows Home Server onto the SSD. As the SSD was quite small, I had to use VMware Converter to “Convert” the VM to another VM with a reduced disk size.

Afterwards, TeamViewer would not connect if I wasn’t logged into the machine (either via the console or RDP). Every time I tried to connect I got a “WaitforConnectFailed” error. Uninstalling and reinstalling TeamViewer was of no help.

As part of the VM conversion process, VMware Converter strips out the network card and installs a new one. This meant that TeamViewer was somehow binding to the old “hidden” network card which would obviously not connect. Using the instructions in the following article (the instructions work with all versions of Windows from XP upwards), I removed the non-present NIC and voilá, it now works as it should. A fairly unique situation, but one I thought was worth documenting.

DayZ Epoch – Dedicated server reboot

So I’ve recently set up a private dedicated server for DayZ Epoch for myself and a few friends. Because we only have a fairly limited time of day that we can play at (what with working and all), I’m only really concerned with rebooting the server once a week.

The server OS does an auto reboot every week, at 3:45 AM, then to allow any patches to finish installing/configuring, I wait 15 minutes before triggering an ArmA II start at 4:00 AM. The batch file I use is:

@echo off
cd /d E:\DayZ_Epoch_Server
start “” “DayZ_Epoch_instance_11_Chernarus.bat”
taskkill /f /im cmd.exe

This is called by the Task Scheduler at 4:00 AM which then runs the default bat file, which if you call directly fails.