For the next part of this series, we’re going to examine another topic that seems to be handled poorly, if at all, in the scripts I have encountered as a sysadmin. That is of persisting the script state.
The obvious thing to say here is that you could use a RDBMS backend. Which is fine and dandy to bring up as a point, but isn’t always the optimal or even a possible solution due to restrictions that may be placed by management pertaining to what technologies can be utilized. Sometimes, overbearing managers will blindly say things like, “Hey now, we don’t want you sysadmins writing any code.” Using something like an RDBMS would be out of the question but writing a script in a native scripting language like PowerShell might be overlooked.
What we will examine in this article is one possible solution, which is using PowerShell native functionality (with no external libraries) to persist the script state to disk. What this buys us is the ability to persist the script state even if the script is interrupted or if the machine running the script is rebooted. The script may just start where it left off and continue chugging along during it’s next execution. It’s not optimal by any means, it’s just a script that is meant to meet the weird set of requirements you might encounter as a sysadmin and to, hopefully, provoke thought from my readers about how they can apply techniques to their own scripts and automation.
Let’s look at an example of this technique:
<# Create persistence layer in same directory as script #>
$workingDirectory = Split-Path $MyInvocation.MyCommand.Source
$stateDatabase = $($workingDirectory + '\state.xml')
<# Quick and dirty list of hosts to monitor #>
$hosts = @(
'127.0.0.1',
'8.8.8.8',
'blarg'
)
<# Our "script state" is a hashtable of hashtables #>
$scriptState = @{}
<# Populate script state with our list of hosts with default values #>
foreach($key in $hosts) {
$value = @{
"Status"="Offline";
"LastSeen"="Never";
}
$scriptState.Add($key,$value)
}
<# Load the stored state data if it has already been persisted to disk #>
if (Test-Path -Path $stateDatabase) {
$scriptState = Import-Clixml -Path $stateDatabase
}
<# Create our ping object outside of loop #>
$ping = New-Object System.Net.NetworkInformation.Ping
while ($true) {
<# Iterate through elements in script state and test connectivity, update Status and LastSeen #>
foreach($entry in $scriptState.Clone().GetEnumerator()) {
$currentState = $entry.Value["Status"]
$pingReply = $null
$ErrorActionPreference = "SilentlyContinue"
$pingReply = $ping.Send($entry.Key)
$ErrorActionPreference = "Continue"
if ($pingReply.Status -ne "Success") {
$scriptState[$entry.Key]['Status'] = "Offline"
} else {
$scriptState[$entry.Key]['Status'] = "Online"
$scriptState[$entry.Key]['LastSeen'] = Get-Date
}
if ($currentState -ne $entry.Value["Status"]) {
Write-Host $($entry.Key + ' changed Status to ' + $entry.Value["Status"])
}
}
Export-Clixml -InputObject $scriptState -Path $stateDatabase
Sleep -Seconds 5
}
Obviously, this little script doesn’t do much, but the magic is the Import-Clixml
and Export-Clixml
cmdlets that allow you to do something pretty novel, which is serializing the data structure (the hashtable of hashtables) we have created in memory to disk. Every pass through our while
loop we’re updating the status of each of the hosts based on connectivity. When our script starts, it checks if the database has already been written to disk an deserializes it into memory so we can continue where we left off from.
This is not a sophisticated approach by any means— it’s just a little PowerShell to point out a common oversight in scripting and automation, which is lack of handling for interruptions and the ability to seamlessly continue from when you left off with minimal data loss. I am fully aware that if a power interruption happened at exactly the right time, it is possible the .xml
database might in the middle of a write to disk, so for production code I would recommend extending the functionality to utilize multiple copies of the database in case of a failure. Another point to mention is that breaking this script down into separate functions would be appropriate, especially as more complexity is added, but I avoided doing so to allow the reader to read the logic from beginning to end without having to jump around the script.
In conclusion, I don’t believe what I have presented is groundbreaking or world-changing for anyone. I just want to bring awareness to the dangers of automating tasks without thinking at least a little bit of the design. Your script may do exactly what you desire, but it’s much more important to consider what it does in the exceptional case, not just the “happy path”. Additionally, it may be possible that having a way to persist state at all may open up possibilities that a sysadmin has never even considered.
Comments !