Building a PowerShell Module - Part 3 - JSON Config Files are Awesome

JSON support in PowerShell is a beautiful thing. Creating a configuration file for PSSpeedTest to store default iPerf3 server information in is a breeze with the usage of two fairly straightforward functions: Get-SpeedTestConfig and Set-SpeedTestConfig.

Part 3 - JSON Configuration File Getting/Setting


The source for Get-SpeedTestConfig can be found here on GitHub, but let’s dive in to the meat of the function.

try {  
  Write-Verbose -Message "Getting content of config.json and returning as a PSCustomObject."  
  $config = Get-Content -Path "$($PSScriptRoot | Split-Path -Parent)\config.json" -ErrorAction "Stop" | ConvertFrom-Json

  if ($PassThru){  
    return $config  
  else {  
    Write-Host "Internet Server: $($config.defaultInternetServer.defaultServer)"  
    Write-Host "Internet Port: $($config.defaultInternetServer.defaultPort)"  
    Write-Host "Local Server: $($config.defaultLocalServer.defaultServer)"  
    Write-Host "Local Port: $($config.defaultLocalServer.defaultPort)"  
catch {  
  throw "Can't find the JSON configuration file. Use ‘Set-SpeedTestConfig' to create one."  

First and foremost, there’s a chance that a configuration file for PSSpeedTest does not already exist; blindly pulling from a path to a JSON file would be bad practice without error handling, so a try/catch block is the first thing that should be put in place. Depending on your own thoughts and opinions on error handling, you may want to use Test-Path for this purpose rather than try/catch.

If a configuration file is present at the expected path and no error is thrown, the rest of the function is simply just reading a text file and converting it to a navigable object with Get-Content and ConvertFrom-Json.

In the full source of the function you will see that there is a $PassThru switch parameter present; if this is specified by the user, the object resulting from ConvertFrom-Json will be returned and output will be silenced. This is so that administrators can manipulate this object without directly affecting the stored configuration if they so choose.


Source for Set-SpeedTestConfig is found here. Let’s view this function as three distinct sections; two for error handling and one for finalizing and setting the configuration.

No Worries About a Non-Existent Configuration

As we discussed before with Get-SpeedTestConfig, it’s never safe to assume that a configuration file already exists. This function is all about setting a config, however, so I’d rather not throw an error if one doesn’t exist; I’d rather create a blank one that will then be set by the calling user.

try {  
  Write-Verbose -Message "Trying Get-SpeedTestConfig before Set-SpeedTestConfig."  
  $config = Get-SpeedTestConfig -PassThru  
  Write-Verbose -Message "Stored config.json found."  
catch {  
  Write-Verbose -Message "No configuration found - starting with empty configuration."  
  $jsonString = @"
{ "defaultPort" : "5201",  
"defaultLocalServer": {  
"defaultServer" : "",  
"defaultPort" : ""  
"defaultInternetServer" : {  
"defaultServer" : "",  
"defaultPort" : ""  
  $config = $jsonString | ConvertFrom-Json  

If the call to Get-SpeedTestConfig doesn’t find an existing configuration file, an empty config is generated via a multi-line JSON string that is piped to ConvertFrom-Json. This allows for compatibility with the rest of the function as if a file was found and read with Get-Content.

No Ports Without Servers

Next, it’s important to consider what combinations of parameters make sense when setting a configuration as well as what configuration elements are already set. Having ports defined without an associated server doesn’t make sense because there is no default server for iPerf3, of course. Having servers without ports is fine because iPerf3 can run on a default port which is accounted for in my module.

# Detailed parameter validation against current configuration  
if ($InternetPort `  
-and (!($InternetServer)) `  
-and (!($config.defaultInternetServer.defaultServer))) {  
  throw "Cannot set an Internet port with an empty InternetServer setting."  
if ($LocalPort `  
-and (!($LocalServer)) `  
-and (!($config.defaultLocalServer.defaultServer))) {  
  throw "Cannot set a Local port with an empty LocalServer setting."  

If either type of port is being set and an associated server is not also being set or is not pre-existing, an error is thrown.

Set and Write Out the Object

Finally, the JSON object is modified to store any arguments given by the calling user, converted to a JSON string, and saved in the configuration file.

if ($InternetServer) {$config.defaultInternetServer.defaultServer = $InternetServer}  
if ($InternetPort) {$config.defaultInternetServer.defaultPort = $InternetPort}  
if ($LocalServer) {$config.defaultLocalServer.defaultServer = $LocalServer}  
if ($LocalPort) {$config.defaultLocalServer.defaultPort = $LocalPort}

Write-Verbose -Message "Setting config.json."  
$config `  
| ConvertTo-Json `  
| Set-Content -Path "$($PSScriptRoot | Split-Path -Parent)\config.json"