The Script Logic Behind a PowerShell App

In Part 2 of this series, we continue to explore how to develop PowerShell applications – this time focusing on the script’s logic.

Brien Posey

October 5, 2023

7 Min Read
abstract blue futuristic graphic with cogs and wheels system
Alamy

As I explained in Part 1 of this series, due to the complexity of my weather app script, I built it in two sections and later merged them. One section focused on the script’s user interface, while the other dealt with the script’s logic. In this article, I will show you how the basic script logic works, particularly as it relates to the external data sources I used.

  • Part 1: The first article delves into the resources used by the app. Additionally, I discuss strategies for developing complex PowerShell scripts.

  • Part 2: The second article focuses on the script’s logic, with an emphasis on acquiring and parsing the necessary data.

  • Part 3: I address some of the trickier aspects of the GUI. I will also provide my app’s full source code at the end of the article.

A Look at the Script (Without the GUI)

Here is my original script without the graphical user interface. It is worth noting that the Write-Host commands in this script are solely for data verification purposes. Additionally, please be aware that I made some minor changes to the logic in the final version of the script, which I will provide at the end of Part 3.

Here is the script:

# Acquire Zip Code$MyZip='29044'# Import the Zip Code Database# The data came from: https://simplemaps.com/data/us-zips$ZipData=Import-CSV -Path C:ScriptsZipCodeDataUSZips.csv$MyData=$ZipData | Where-Object {$_.Zip -eq $MyZip}# Acquire Latitude, Longitude, City, and State$MyLat = $MyData.Lat$MyLong = $MyData.lng$MyCity = $MyData.City$MyState = $MyData.State_Name# Normalize Latitude and Longitude by dropping the last digit of each$MyLat=$MyLat.SubString(0,$MyLat.Length-1)$MyLong=$MyLong.SubString(0,$MyLong.Length-1)# Verify Latitude, Longitude, City, and StateWrite-Host $MyLatWrite-Host $MyLongWrite-Host $MyCityWrite-Host $MyState# Assemble URI that will be used to retrieve the forecast grid$URI='https://api.weather.gov/points/'+$MyLat+','+$MyLongWrite-Host $URI# Make API Call to retrieve weather forecast grid$Grid=Invoke-RestMethod -Method Get -URI $URI$ForecastURI=$Grid.Properties.Forecast# Verify Forecast Grid URI$ForecastURI# Make API Call to Get the Forecast$RawForecastData=Invoke-RestMethod -Method Get -URI $ForecastURI# Assemble the Data for each day (the period)$PeriodName = @($RawForecastData.Properties.Periods.Name | Select-Object -First 5)$Temperature = @($RawForecastData.Properties.Periods.Temperature | Select-Object -First 5)$Unit = @($RawForecastData.Properties.Periods.TemperatureUnit | Select-Object -First 5)$Wind = @($RawForecastData.Properties.Periods.WindSpeed | Select-Object -First 5)$WindDirection = @($RawForecastData.Properties.Periods.WindDirection | Select-Object -First 5)$ShortForecast = @($RawForecastData.Properties.Periods.ShortForecast | Select-Object -First 5)$DetailedForecast = @($RawForecastData.Properties.Periods.DetailedForecast | Select-Object -First 5)$IsDayTime = @($RawForecastData.Properties.Periods.IsDaytime | Select-Object -First 5)# Define array for storing icon references$Icon=@()# Loop to display dataFor ($Period=0; $Period -le 4; $Period++){            Write-Host            Write-Host $PeriodName[$Period]            Write-Host $Temperature[$Period] $Unit[$Period]            Write-Host 'Wind: '$WindDirection[$Period] $Wind[$Period]            Write-Host $ShortForecast[$Period]            Write-Host $DetailedForecast[$Period]            Write-Host $IsDayTime[$Period]            Write-Host# Associate icon with forecast                        Switch ($ShortForecast[$Period])                                    {                                    'Clear' {$Icon+='NightClear.jpg'}                                    'Sunny' {$Icon+='Sunny.jpg'}                                    Default {$Icon+='Question.jpg'}                                    }            Write-Host $Icon[$Period]            Write-Host            }

How the Script Logic Works

As you saw in Part 1, the very first thing that the finished script does is request a zip code from the user. The above script contains a hardcoded zip code that I used for testing, just so I wouldn’t have to repeatedly enter a zip code while debugging.

Following the prompt for a zip code, the script imports the CSV file that contains all the zip code data. I do this by using the Import-CSV command, followed by the path and filename of the CSV file.

The line of code shown below filters the CSV file. It’s one of the more important lines of the script:

$MyData=$ZipData | Where-Object {$_.Zip -eq $MyZip}

This line of code creates an object called $MyData. Instead of including all the data from the CSV file in this object, I am  using the Where-Object cmdlet to filter the data based on the entered zip code. That way, the $MyData object will only contain data relevant to the specified location. After that, I create four variables, each of which point to specific pieces of data stored within the $MyData object. These variables capture the zip code’s latitude, its longitude, the name of the nearest city, and the state in which the zip code is located.

I am extracting the latitude and longitude because the National Weather Service API requires that you provide location data in the form of coordinates. However, the latitude and longitude data from the ZIP file can’t be used in its original form. The ZIP file’s coordinates have five decimal places, whereas the National Weather Service API requires coordinates with only four decimal places. To address this, I used PowerShell string manipulation to remove the last digit from the coordinates. Here are the commands I used, with the Length-1 portion being what shortens the strings:

$MyLat=$MyLat.SubString(0,$MyLat.Length-1)$MyLong=$MyLong.SubString(0,$MyLong.Length-1)

At this point, the script has everything it needs to make the first of two required API calls. The National Weather Service API provides weather forecast data by using a grid system instead of coordinates. Therefore, the first API call uses coordinates but acquires a secondary URI associated with the corresponding location grid. The script then calls that secondary URI and downloads the actual weather data.

The process of making the first API call simply involves appending the latitude and longitude coordinates to a known URI. Afterward, the script uses the Invoke-RestMethod command to obtain the grid data. You can see the commands listed below:

# Assemble URI that will be used to retrieve the forecast grid$URI='https://api.weather.gov/points/'+$MyLat+','+$MyLong# Make API Call to retrieve weather forecast grid$Grid=Invoke-RestMethod -Method Get -URI $URI

The URI needed for the second API call (the call that downloads the actual weather data) is included in the response to the first API call. In the API call shown above, you will notice it creates a PowerShell object called $Grid. You can find the URI needed for the second API call at $Grid.Properties.Forecast. With this URI in hand, you can execute the second API call using these commands:

$ForecastURI=$Grid.Properties.Forecast# Verify Forecast Grid URI$ForecastURI# Make API Call to Get the Forecast$RawForecastData=Invoke-RestMethod -Method Get -URI $ForecastURI

The $RawForecastData object created by the above command contains a vast amount of data, much  more than what this script utilizes. Therefore, the next thing the script does is extract the data that is needed. Given that the API response contains data for multiple days, I have created several arrays, one for each data type. For example, the $Temperature array stores today’s temperature, tonight’s temperature forecast, tomorrow morning’s temperature forecast, and so forth. Likewise, the $Wind array stores the wind speed for today, tonight’s forecasted windspeed, tomorrow’s forecasted windspeed – and the list goes on.

It’s important to understand that although I’m using various arrays to store different types of data, these arrays are interconnected. Each array position references a specific point in time. For example, $Temperature[0] (the first position in the $Temperature array) generally contains today’s temperature. Similarly, $Wind[0] (the first position in the $Wind array) contains today’s wind speed. Typically, the [1] position in each array would represent the overnight forecast, and [2] would contain tomorrow’s data. Of course, these positions would be shifted if you ran the script at night. Nevertheless, each position within an array aligns with the same position in all the other arrays.

A final important detail about the script’s logic is that while the API does provide links to weather icons, displaying those icons in PowerShell requires a lot of manipulation. This is why I opted to use an external collection of icons instead (see Figure 1).

PowerShell app icons

Complex PowerShell 1-2_0

Figure 1. An example of the icons representing different weather conditions.

Near the end of the script, you will find a switch statement that associates the short forecast with a specific icon. For example, if the short forecast is “Sunny,” PowerShell will display the icon featuring a picture of the sun. While the final script has some minor variations in its logic, the basic principle is similar to what you see below.

Switch ($ShortForecast[$Period])                                    {                                    'Clear' {$Icon+='NightClear.jpg'}                                    'Sunny' {$Icon+='Sunny.jpg'}                                    Default {$Icon+='Question.jpg'}                                    }

In the interest of time, I have only created icons for “Sunny” and “Clear,” but it is easy to add more icons to the list. However, you will notice that the default icon is Question.jpg. This is because although the $ShortForecast object often contains a single word (such as “sunny,” “clear,” “rainy,” etc.), this isn’t always the case. The $ShortForecast might include phrases like “patchy fog clearing later.” Since it’s tough to anticipate all possible multi-word short forecasts, I have created a question-mark icon that can represent any conditions not explicitly programmed.

Now that I have covered how the data portion of the script works, I will shift to the user interface, which is discussed in Part 3. At the end of Part 3, I will provide the full source code for the finished script.

About the Author

Brien Posey

Brien Posey is a bestselling technology author, a speaker, and a 20X Microsoft MVP. In addition to his ongoing work in IT, Posey has spent the last several years training as a commercial astronaut candidate in preparation to fly on a mission to study polar mesospheric clouds from space.

https://brienposey.com/

Sign up for the ITPro Today newsletter
Stay on top of the IT universe with commentary, news analysis, how-to's, and tips delivered to your inbox daily.

You May Also Like