Tuesday, January 31, 2017

The new way to check computer information with PowerShell and WMF 5.1

Microsoft has recently made the Windows Management Framework (WMF) 5.1 bits available for download for Windows 7, Windows 8.1, Windows Server 2008 R2, Windows Server 2012, and Windows Server 2012 R2. This version of the framework brings a useful discovery cmdlet named Get-ComputerInfo.

Let's have a quick look at the information it can provide.
When you run it for the first time you can see that it tries to fetch information around the following objects, as you can read in the progress bar:

  • Operating system
  • Hot-patch
  • Registry
  • Bios
  • Motherboard
  • Computer
  • Processor
  • Network adapter


All the returned values are consolidated in a single easy-to-read PSCustomObject which boasts 181 properties. The nice thing of using this cmdlet is that you don't get all the noisy information that is commonly returned by Get-WMIObject, like the genus, class, superclass or namespace properties. You get directly the information in a nicely formatted way like the one returned by running Get-CIMInstance. But in a single cmdlet.

In other words, with a single cmdlet you can get the equivalent of running Get-CIMInstance against those instances: Win32_BIOS, Win32_ComputerSystem, Win32_OperatingSystem, Win32_TimeZone, plus a bunch of miscellaneous information such as the logon server, which you used to retrieve from HKCU\Volatile Environment\LOGONSERVER or from your $env:LOGONSERVER variable, or the calculated property Uptime, which is the difference between now (as you could get from Get-Date) and the LastBootUpTime value from Win32_OperatingSystem (check my post on Windows Boot Time if you want to know more on how this is calculated), or also the information related to the Device Guard feature:

DeviceGuardRequiredSecurityProperties
DeviceGuardAvailableSecurityProperties
DeviceGuardSecurityServicesConfigured
DeviceGuardSecurityServicesRunning
DeviceGuardCodeIntegrityPolicyEnforcementStatus
DeviceGuardUserModeCodeIntegrityPolicyEnforcementStatus

It is worth noticing that Get-ComputerInfo returns also information about processor support for Second Level Address Translation as well as for other Hyper-V requirements (I wrote a blog post about this a while back) without getting your hands dirty with CoreInfo or SystemInfo:

HyperVisorPresent                                       : True
HyperVRequirementDataExecutionPreventionAvailable       :
HyperVRequirementSecondLevelAddressTranslation          :
HyperVRequirementVirtualizationFirmwareEnabled          :
HyperVRequirementVMMonitorModeExtensions                :

It also returns the list of hotfixed installed without having to launch Get-HotFix: just use Select-Object with -ExpandProperty and you'll get the unique id of each fix, as well as the install date and the description:

Get-ComputerInfo | Select-Object -ExpandProperty OsHotFixes

HotFixID  Description     InstalledOn FixComments
--------  -----------     ----------- -----------
KB3192137 Update          9/12/2016
KB3199986 Update          11/17/2016
KB3206632 Security Update 1/3/2017

What else, you're asking?

The nice thing I would add is that you can easily limit the subset or returned information by passing to this cmdlet exact or wildcarded property names:

Get-ComputerInfo *mem*

CsTotalPhysicalMemory      : 17179398144
CsPhyicallyInstalledMemory : 16777216
OsTotalVisibleMemorySize   : 16776756
OsFreePhysicalMemory       : 11959208
OsTotalVirtualMemorySize   : 19267124
OsFreeVirtualMemory        : 14368488
OsInUseVirtualMemory       : 4898636
OsMaxProcessMemorySize     : 137438953344

Or you can generate calculated properties, like in the following example:

Get-ComputerInfo -Property "CsProcessors","CsNumberOfProcessors","CsNumberOfLogicalProcessors","CsPhyicallyInstalledMemory" | Format-List "CsProcessors","CsNumberOfProcessors","CsNumberOfLogicalProcessors",@{label="CsPhyicallyInstalledMemory (GB)";expression={$_.CsPhyicallyInstalledMemory/1MB}}

CsProcessors                    : {Intel(R) Xeon(R) CPU E5-2660 v3 @ 2.60GHz, Intel(R) Xeon(R) CPU E5-2660 v3 @ 2.60GHz}
CsNumberOfProcessors            : 2
CsNumberOfLogicalProcessors     : 2
CsPhyicallyInstalledMemory (GB) : 16

So, to sum up Get-ComputerInfo is a very useful cmdlet, but lacks native remoting capabilities, which is something that will come for sure in the future. There is a open request for this on UserVoice by fellow MVP Jan Egil Ring, which I invite you to vote for.

To finish with, know that this cmdlet has a 'gin' alias, for faster use:

Get-Command gin

CommandType     Name                         Version    Source
-----------     ----                         -------    ------
Alias           gin -> Get-ComputerInfo      3.1.0.0    Microsoft.PowerShell.Management

The journey to a robust administration language continues.


UPDATE February 9th 2017: Get-ComputerInfo will onyl work on Windows 10 if WinRM is enabled due to a bug in the source code. So if Get-ComputerInfo runs slowly and returns empty values, just run Enable-PSRemoting -force on your computer and you are good to go. This bug has been fixed but will have to wait for a future PowerShell release before being able to use the corrected version.

Tuesday, January 24, 2017

Auditing your VMware environment with PowerCLI

I am a great fan of VMware PowerCLI, and even if I don't have my blog posts on the subject, I have got a plethora of functions that I use pretty often for managing my virtual environment. Recently though, I have been asked to perform a deep audit of all the ESXi servers in our farms, and discovered that I did not have a decent function for the job.

I was stuck in particular on the fact that there is no way to know if my ESX are properly licensed to run Microsoft Windows as guest OS and one of the requirements I had was exactly to check that we had bought and assigned a Windows 2012 R2 Datacenter license to the ESXi hosting Windows VMs.

And I was also asked to provide information on the cost center for the project that was used to buy the physical assets and the licenses.

For sure none of this information is available out of the box and I spent quite a bit of time to decide how to cope with the requirements.

In the end I decided to use the Set-Annotation cmdlet to add additional static information to my ESXi and then to write and advanced function to retrieve a general inventory of the physical server as well as those manually added fields. Set-Annotation is a tiny nice cmdlet that modifies the value of a custom attribute. In my case I have added two custom attributes to my hosts:




And I have a script which does the job of setting the values for those attributes each time I install a new ESXi. Here's a couple examples of how I use this script:

1..9 | % {Set-Annotation -Entity (Get-Vmhost esx$_.myserverfarm.com) -CustomAttribute Windows-License -Value 'Datacenter-2012'}
or
Set-Annotation -Entity (Get-Vmhost esx1.myserverfarm.com) -CustomAttribute Project -Value 'Project Name'
Once I have set those two attributes everywhere, I can proceed to run my function Get-ESXAudit, which retrieves all the information I need and return it in form of a PowerShell object (I hope my friend and fellow MVP Luc Dekens will be forgiving if this is a too basic function).

In my function, to build the object I rely mainly on three pieces of information:
  1. the one returned by Get-VmHost which basically contains all the hardware information, as you can see in the following screenshot

  2. the one returned by Get-View, which returns a view of all the object with a higher level of detail, like the serial number or the EVC mode for a host
  3. the one returned by the License Manager, like the assigned license key or the number of licenses used
Once I have put the contents of these three information providers into three variables, I can use those to build up my custom object. Here's the variable assignment part:
And here's the first part of the creation of the custom object:
During the creation of the object I use the Get-Annotation cmdlet to fetch the custom attributes I have added to my hosts:
In order to follow the execution of this function, I find it useful to add a progress bar through the use of the Write-Progress cmdlet:
The last important thing to note is the two phases of setup and tear down of the connections to the VIservers, which is pretty important if you want to keep your memory footprint low:

Now here's the full code of the Get-ESXAudit function, which you can also find on GitHub:
Function Get-ESXAudit

{

<#
.Synopsis
The Get-ESXAudit retrieves complete information about the configuration, licensing and load of one or more VMWARE ESX servers.
.EXAMPLE
Get-ESXAudit -vCenter vcenter.myserverfarm.com -ESX '*'
.EXAMPLE
Get-ESXAudit -vCenter vcenter.myserverfarm.com -ESX 'esx1.myserverfarm.com','esx2.myserverfarm.com'
.EXAMPLE
$auditinfo = Get-ESXAudit -vCenter vcenter.myserverfarm.com -ESX 'esx1.myserverfarm.com','esx2.myserverfarm.com'
$auditinfo | select -skip 1 | ft * -auto
.EXAMPLE
'vcenter1.myserverfarm.com','vcenter2.myserverfarm.com' | Get-ESXAudit -ESX 'esx1.myserverfarm.com','esx2.myserverfarm.com'
.EXAMPLE
Get-ESXAudit -vCenter vcenter.myserverfarm.com -ESX '*' | Select-Object -Skip 1  | ConvertTo-Csv -NoTypeInformation | Out-File "audit-vmware.csv" -Force
.NOTES
happysysadm.com
@sysadm2010
#>


param(
    [parameter(mandatory=$true,valuefrompipeline=$true)]
    [string[]]$vCenter,

    [string[]]$ESX = '*'
    )
        
    Write-Verbose "Adding snapin"
    Add-PSSnapin vmware.vimautomation.core

    Write-Verbose "Cleaning up connections to vcenters"
    Disconnect-VIServer -Server $vCenter -Confirm:$false -ErrorAction SilentlyContinue

    Write-Verbose "Connecting to vcenters"
    Connect-VIServer -Server $vCenter

    Write-Verbose "Connecting to license manager"
    $ServiceInstance = Get-View ServiceInstance
    $LicenseManager = Get-View $ServiceInstance.Content.LicenseManager
    $LicenseManagerAssign = Get-View $LicenseManager.LicenseAssignmentManager

    Write-Verbose "Retrieving the hosts"
    $VMhosts=Get-VMHost $ESX

    $VMhostsTotal=@()

    Foreach($VMhost in $VMHosts)

        {
        $i++

        Write-Progress -activity "Retrieving $($VMHosts.count) ESX information" `
            -status "Doing $i on $($VMHosts.count)" -PercentComplete (($i / $VMHosts.count)  * 100)

        Write-Verbose "Retrieving general hardware information on $VMhost"
        $VMHostHW = Get-VMHost -Name $VMHost.name

        Write-Verbose "Retrieving a view on the .NET object"
        $VMHostView = $VMHostHW | Get-View

        Write-Verbose "Retrieving the ID"
        $VMhostID = $VMHostView.Config.Host.Value

        Write-Verbose "Retrieving the licence information"
        $VMHostLicInfo = $LicenseManagerAssign.QueryAssignedLicenses($VMhostID)

        Write-Verbose "Creating object"
        $vmhostobject = [PSCustomObject]@{

            Name = $VMHostView.Name

            Manufacturer = $VMHostHW.Manufacturer

            Model = $VMHostHW.Model

            Product = $VMHostView.Config.Product.Name

            Version = $VMHostView.Config.Product.Version

            Sockets = $VMHostView.Hardware.CpuInfo.NumCpuPackages

            CPUCores = $VMHostView.Hardware.CpuInfo.NumCpuCores

            LicenseVersion = $VMHostLicInfo.AssignedLicense.Name | Select -Unique

            LicenseKey = $VMHostLicInfo.AssignedLicense.LicenseKey | Select -Unique

            TotalLicense = $VMHostLicInfo.AssignedLicense.Total | Select -Unique

            UsedLicense = $VMHostLicInfo.AssignedLicense.Used | Select -Unique

            CostUnit = $VMHostLicInfo.AssignedLicense.CostUnit | Select -Unique

            WindowsLicense = ($VMHostHW | Get-Annotation | where name -eq 'Windows-License').value

            Project = ($VMHostHW | Get-Annotation | where name -eq 'Project').value

            VMs = ($VMHostHW | Get-VM).count

            Memory = [math]::round(($VMHostHW.MemoryTotalMB/1KB),0)
        
            Memoryused = [math]::round(($VMHostHW.MemoryUsageMB/1KB),0)

            Percentusedmem = [int]((100/$VMHostHW.MemoryTotalMB ) * $VMHostHW.MemoryUsageMB)

            CpuTotalMhz = $VMHostHW.CpuTotalMhz
                                    
            CpuUsageMhz = $VMHostHW.CpuUsageMhz
                                    
            Percentusedcpu = [int]((100/$VMHostHW.CpuTotalMhz)*$VMHostHW.CpuUsageMhz)

            Parent = $VMHostHW.Parent

            Serial = ($VMHostView.Hardware.SystemInfo.OtherIdentifyingInfo | where {$_.IdentifierType.Key -eq “ServiceTag”}).IdentifierValue

            EVCMode = ((($VMHostHW).parent | Get-View).summary).CurrentEVCModeKey

            MaxEVCMode = ($VMHostView | select -ExpandProperty summary).maxevcmodekey

            Cluster = $VMHostHW.parent.name

            CPU = $VMHostHW.ProcessorType
        
            }

            Write-Verbose "Object created"

            $VMhostsTotal += $vmhostobject

        }

    Write-Verbose "Cleaning up connections to vcenters"
    Disconnect-VIServer -Server $vCenter -Confirm:$False -Force

    write-verbose "Returning all the objects"
    return $VMhostsTotal

} # End Function

Feel free to contribute to this function or to suggest ways to improve it. If you have any question, do not hesitate to post it in the comments and I'll do my best to answer.

Wednesday, January 18, 2017

Getting weather data with PowerShell and other funny things you can do in just a line of code

I am always positively impressed when I see how easy it is today to get pretty much any kind of information from the Internet in a structured manner and re-use it for your own interest. A bridge exists today between all 'those scattered data' up there and the use I can make of them. This bridge is made up of three keywords to me: RESTful API, JSON and PowerShell.

Let me show you an example.

As you already know if you have been actively following my blog, I am working on a module to manage my home pellet stove, and, I am adding to it a bunch of functions to corroborate my findings on internal temperature trends with weather data coming from external weather data providers (I don't have a weather station at home and don't want to invest on one).

It didn't take me long to discover the existence of a free weather data provider, named OpenWeatherMap, since fellow MVP Iris Classon wrote a blog post about it last November.

In her article, which is C#-oriented, she explains how to consume the provided JSON data as C# objects. In my case that part of the job is easily accomplished by using the now well-known Invoke-RestMethod cmdlet, which makes PowerShell capable of interacting with remote RESTful web services and return, oh that's so good, a ready to use PSObject, as you can read in the doc:

This means that once you have the required API key to query the OpenWeatherMap (which by the way is free as long as you stick to max 60 calls per minute), you can get the current weather for any place in the world in just one line of code. Long live all those open APIs:


Now since I want to be able to harvest those data in a more robust manner, I have decided to build a function around that line of code. It's a bit overkill, I know, but that allows me also to take advantage of the last three functions I presented on this blog post, and you'll see the result is not that bad:
  1. the function 'Start-RoboCommand' which helps me in being assured that the data I want to retrieve from the Internet are actually fetched
  2. the function 'Get-WindDirection', which I use to convert the wind direction in degrees into something more readable, like the italianate wind name
  3. the function 'Get-WindForce', which returns a description of the current wind by using the Beaufort Scale
The function I wrote, which I called Get-LiveWeather, takes three parameters, $City, which also accepts values coming through the pipeline, $Unit, whose input is validated with [ValidateSet], and $Key, which is the OpenWeatherMap API key we talked above.

Here's a screenshot of the parameter declaration:


Now let's have a look at the Process() block, which is where we provide record-by-record processing for elements coming down the pipeline:


First of all I am going with a Try{} Catch{} to try to resolve the passed city name into a OpenWeatherMap ID. If this does not succeed, then I can print an error and move on with the next city name, if any. On the other side, if the city name is correctly spelled, then I can actually fire my Start-RoboCommand function to run Invoke-RestMethod. The output is then stored in a $Weather variable. 
Starting from the raw content of that variable I build a [PSCustomObject] the way I want to present the data. This is where I make an external call to the other functions I wrote to calculate the wind force and the italianate wind name.

Here's a couple of example of how you can use this function and the output you can obtain:

Get-LiveWeather -City 'San Francisco' -key $yourkey

City_Name            : San Francisco
Temperature          : 281.64
Humidity             : 87
Pressure             : 1018
Weather-description  : {light rain, mist}
Wind_Speed           : 3.6
Wind_Direction       : South
Wind_Italianate_Name : Ostro
Wind_Degrees         : 170
Wind_Force           : Gentle breeze

Get-LiveWeather -City 'paris','london','roma','berlin' -Unit Metric -Key $yourkey | ft * -AutoSize


Nice stuff. Now I started to wonder whether there is a API out there which allows me to find all the capital cities in the world and run my function against it. The quest didn't last long since I pretty soon found out this web that provides me exactly what I need:

Invoke-RestMethod https://restcountries.eu/rest/v1/all | ft * -autosize

The resulting table is here:


Happily enough there is a 'Capital' property (the last column on the right) which I can pass to my function to retrieve the weather in all the capital cities of the world in no more than a single line of code:

(Invoke-RestMethod https://restcountries.eu/rest/v1/all).capital | Get-LiveWeather -Unit Metric -Key $yourkey | ft * -AutoSize


Now this is becoming fun. I could imagine to do pretty much anything over this initial oneliner, such as to determine the hottest town of the moment:

(Invoke-RestMethod https://restcountries.eu/rest/v1/all).capital | Get-LiveWeather -Unit Metric -Key $yourkey | sort Temperature -desc | select -first 1 | ft * -AutoSize

or to find the windiest place:

(Invoke-RestMethod https://restcountries.eu/rest/v1/all).capital | Get-LiveWeather -Unit Metric -Key $yourkey | sort Wind_Speed -desc | select -first 1 | ft * -AutoSize

See the power of the shell here? I always find it awesome that we are able to get so much data in a so easy way and print it in a very readable output.

Here's the full code of my function, which is also available on GitHub:

<#
.Synopsis
   Get-LiveWeather is a function that retrieves current weather information from OpenWeatherMap
.EXAMPLE
    Get-LiveWeather -City 'San Francisco' -key 'yourkey'
.EXAMPLE
   Get-LiveWeather -City 'Paris','London','Roma','Berlin' -Unit Metric -Key 'yourkey' | ft * -AutoSize
.EXAMPLE
   (Invoke-RestMethod https://restcountries.eu/rest/v1/all).capital | Get-LiveWeather -Unit Metric -Key 'yourkey' | ft * -AutoSize
.NOTES
   happysysadm.com
   @sysadm2010
#>
function Get-LiveWeather
{
    [CmdletBinding()]
    Param
    (
        # City Name
        [Parameter(Mandatory=$true,
                   ValueFromPipeline=$true,
                   Position=0)]
        [string[]]$City,

        # Standard, metric, or imperial units
         [ValidateSet("Standard","Metric","Imperial")]
        [string]$Unit='Standard',

        # Openweather key
        [Parameter(Mandatory=$true)]
        [string]$Key
    )
    Process
    {
    foreach($Cityname in $City)
        {
        $ok = $false
        try{
            $city_id = (Invoke-RestMethod "api.openweathermap.org/data/2.5/weather?q=$cityname&APPID=$key&units=$Unit" -ErrorAction Stop).id
            $ok = $true
        }
        catch{
            Write-Error "$Cityname not found...."
        }
        if($ok) {
            $Weather = Start-RoboCommand -Command 'Invoke-RestMethod' `
                            -Args @{ URI = "api.openweathermap.org/data/2.5/weather?id=$city_ID&APPID=$app_ID&units=$Unit" } `
                            -Count 5 -DelaySec 4 -LogFile error.log
            [double]$WeatherWind = $Weather.wind.speed
            $CityWeatherObject = [PSCustomObject]@{
                "City_Name" = $Cityname
                "Temperature" = $Weather.main.temp
                "Humidity" = $Weather.main.humidity
                "Pressure" = $Weather.main.pressure
                "Weather-description" = $Weather.weather.description
                "Wind_Speed" = $WeatherWind
                "Wind_Direction" = (Get-WindDirection $Weather.wind.deg).direction
                "Wind_Italianate_Name" = (Get-WindDirection $Weather.wind.deg).name
                "Wind_Degrees" = $Weather.wind.deg
                "Wind_Force" = Get-WindForce $WeatherWind -Language EN
                }
            $CityWeatherObject
            }
        }
    }
}

Kudos to OpenWeatherMap and to RestCountries.eu for their work.

PowerShell rocks.

Thursday, January 12, 2017

A PowerShell function to translate wind speed to Beaufort scale numbers

While last week I have shown you how to write a PowerShell function aimed at converting the direction of wind in degrees to the italianate wind name, this time I am introducing a function, called Get-WindForce that translates the speed of the wind in m/s to a readable description taken from the Beaufort Scale.

While the function that I wrote last week uses the Italian wind names of Medieval origin, which aren't translated because they have become an international standard still used nowadays, the function that gives you a description of the wind force has to be developed in a way to return the information in the language of the reader.

That's why I had to add a -Language optional parameter that tells the function the language you want to use in the output. Based on the passed value it relies on the Switch statement to choose the proper values to display:


Then it uses again the Switch statement to pick the description in the given language:


Nothing especially difficult here, apart from the fact that it took quite a bit of typing to get all the possible wind speeds and descriptions in the four languages that I have pre-loaded in my function; English, Italian, French and German.

Here's the full code of my function:

function Get-WindForce {

<#
.Synopsis
   Returns wind force from speed in m/s
.DESCRIPTION
   Returns wind force in a give language from speed in m/s
.EXAMPLE
   Get-WindForce -speed 2 -language EN
.EXAMPLE
   Get-WindForce -speed 31.5 -language IT
.EXAMPLE
    15,40 | Get-WindForce -Language FR -Verbose
.NOTES
   happysysadm.com
   @sysadm2010
#>
    [CmdletBinding()]
    [OutputType([string])]
    Param
    (
        # Speed of wind in m/s
        [Parameter(Mandatory=$true,ValueFromPipeline=$true)]
        [double]$Speed,

        # Language to use for the output of the wind force
        [string]$Language = 'EN'
    )
    
    Process {
    
        Write-Verbose "working on $speed m/s"
        $windforce = switch ($speed) {
            {$_ -lt 0.3} { @('Calm','Calma','Calme','WindStille') }
            {($_ -ge 0.3) -and ($_ -le 1.5)} { @('Light air','Bava di vento','Très légère brise','Leichter Zug') }
            {($_ -ge 1.6) -and ($_ -le 3.3)} { @('Light breeze','Brezza leggera','Légère brise','Leichte Brise') }
            {($_ -ge 3.4) -and ($_ -le 5.5)} { @('Gentle breeze','Brezza testa','Petite brise','Schwache Brise') }
            {($_ -ge 5.6) -and ($_ -le 7.9)} { @('Moderate breeze','Vento moderato','Jolie brise','Mäßige Brise') }
            {($_ -ge 8) -and ($_ -le 10.7)} { @('Fresh breeze','Vento teso','Bonne brise','Frische Brise') }
            {($_ -ge 10.8) -and ($_ -le 13.8)} { @('Strong breeze','Vento fresco','Vent frais','Starker Wind') }
            {($_ -ge 13.9) -and ($_ -le 17.1)} { @('Near gale','Vento forte','Grand frais','Steifer Wind') } 
            {($_ -ge 17.2) -and ($_ -le 20.7)} { @('Gale','Burrasca','Coup de vent','Stürmischer Wind') }
            {($_ -ge 20.8) -and ($_ -le 24.4)} { @('Strong gale','Burrasca forte','Fort coup de vent','Sturm') }
            {($_ -ge 24.5) -and ($_ -le 28.4)} { @('Storm','Tempesta','Tempête','Schwerer Sturm') }
            {($_ -ge 28.5) -and ($_ -le 32.6)} { @('Violent storm','Fortunale','Violent tempête','Orkanartiger Sturm') }
            {$_ -ge 32.7} { @('Hurricane','Uragano','Ouragan','Orkan') }
            default { 'NA','NA','NA','NA' }
            }

        Write-Verbose "Printing in choosen language: $Language"
        switch ($language) {
            'EN' {$windforce[0]}
            'IT' {$windforce[1]}
            'FR' {$windforce[2]}
            'DE' {$windforce[3]}
            Default {$windforce[0]}
            }

    }
   
}

Feel free to adapt this function to you own language. In my case I am using this function as an additional function to the module I am writing to manage my pellet stove. To be more specific, I am using those few lines of code to translate the raw information I get from an external weather data provider, but I'll talk about that part in a future post. Stay tuned for more PowerShell for Domotics!


UPDATE: It looks like my function has been improved: check out MCT Carnegie Johnson version on Github.

Monday, January 2, 2017

A PowerShell function to convert wind degrees to compass directions and italianate wind names

Winter has arrived and since there is no Windows Phone app for managing my pellet stove, I am working on a PowerShell module so that I know exactly how much my stove consumes by fetching the relevant data from the onboard management unit.

The module I am writing allows me to turn on and off my stove, and to configure it to suit my needs.

I have also added to this module a couple of additional functions to build a relation between the activity of my home stove with the data taken from the external weather data provider (I'll talk about this in a future post).

Now I live in a region where there can be strong winds that lower the temperature, so I thought necessary to add to my module a function specifically aimed at converting the wind direction (in degrees) into something more readable, such as one of the 16 compass headings as well as into its Italianate wind name.

This is what I am going to share with you here today.

I am not going to write an entire blog post on the history of the Rose of the Winds (there's a Wikipedia article for that), but, it is interesting to note that there are three possible ways to express a wind direction:
  1. in degress, such as 90°, 220° etc
  2. through its cardinal (north, south, east, west) and ordinal (northeast, southeast, southwest and northwest) directions
  3. through its italianate wind name
The italianate wind names, also known as the traditional wind names, derivate from the fact that during the 13th and 14th century the Italian was the lingua franca in the whole Mediterranean region, and sailors used it to name winds in an understandable way when crossing with people from from other countries.

So basically you have the following table with the eight principal winds:

Direction Italianate Name
--------- --------------
North     Tramontana
Northeast Grecale or Bora
East      Levante or Oriente
Southeast Scirocco or Exaloc
South     Ostro or Mezzogiorno
Southwest Libeccio or Garbino
West      Ponente or Zephyrus
Northwest Maestrale or Mistral

Now since I want my function to be more precise around the wind direction, I have choosen to adopt the 16-wind compass:

Direction       Italianate Name      
---------       --------------      
North           Tramontana
North Northeast Tramontana-Grecale
Northeast       Grecale
East Northeast  Grecale-Levante
East            Levante
East Southeast  Levante-Scirocco
Southeast       Scirocco
South Southeast Scirocco-Ostro
South           Ostro
South Southwest Ostro-Libeccio
Southwest       Libeccio
West Southwest  Libeccio-Ponente
West            Ponente
West Northwest  Ponente-Mastrale
Northwest       Maestrale
North Northwest Maestrale-Tramontana

This is the function I came up with:

function Get-WindDirection {

<#
.Synopsis
   Returns wind direction
.DESCRIPTION
   Returns wind direction and the italianate wind name
.EXAMPLE
   Get-WindDirection -degress 90
.NOTES
   happysysadm.com
   @sysadm2010
#>

    [CmdletBinding()]
    [OutputType([string])]
    Param
    (
        # Degrees
        [Parameter(Mandatory=$true,ValueFromPipeline=$true)]
        [ValidateRange(0,360)][int]$Degree
    )
    Begin {
        $WindCompassDirection = @("North","North Northeast","Northeast","East Northeast","East","East Southeast", "Southeast", "South Southeast","South","South Southwest","Southwest","West Southwest","West","West Northwest","Northwest","North Northwest","North")
        $WindCompassName = @('Tramontana','Tramontana-Grecale','Grecale','Grecale-Levante','Levante','Levante-Scirocco','Scirocco','Scirocco-Ostro','Ostro','Ostro-Libeccio','Libeccio','Libeccio-Ponente','Ponente','Ponente-Mastrale','Maestrale','Maestrale-Tramontana','Tramontana')
        }

    Process {
        $Sector = $Degree/22.5  #Divide the angle by 22.5 because 360deg/16 directions = 22.5deg/direction change
        Write-Verbose "$Degree is in $Sector sector."
        $Value = "" | Select-Object -Property Direction,Name
        $Value.Direction = $WindCompassDirection[$Sector]
        $Value.Name = $WindCompassName[$Sector]
        return $Value
        }

    End {}
   
}

Let's move to see it in detail and to explain the process of converting wind directions in degrees to text words.

The first step is to divide the given wind angle by 22.5 because 360 degrees divided by 16 directions gives sectors 22.5 degrees wide. So:

PS C:\> 23/22.5
1.02222222222222

which means that a wind coming from 23° is in the first sector, after rounding.

Another example:

PS C:\> 177/22.5
7.86666666666667

which means that a wind coming from 177° is in the 7th sector.

Let's put that value into a $Sector variable:

$Sector = $Degree/22.5

Now I just have to tag each sector by creating an array containing all of them. Actually I want two arrays because I want to be able to print the cardinal and ordinal direction along the italianate wind name.

Each arrays has 17 sectors, not 16, so that I am certain to translate a value such as 359° to North:

$WindCompassDirection = @("North","North Northeast","Northeast","East Northeast","East","East Southeast", "Southeast", "South Southeast","South","South Southwest","Southwest","West Southwest","West","West Northwest","Northwest","North Northwest","North")

$ItalianateWindName = @('Tramontana','Tramontana-Grecale','Grecale','Grecale-Levante','Levante','Levante-Scirocco','Scirocco','Scirocco-Ostro','Ostro','Ostro-Libeccio','Libeccio','Libeccio-Ponente','Ponente','Ponente-Mastrale','Maestrale','Maestrale-Tramontana','Tramontana')

Now you already know for sure that you can echo back the value of an item (such as number 4) in a indexed PowerShell array with the following code:

$array[4]

This is the technique I use to access the value inside the array composed of wind names:

$value.Direction = $WindCompassDirection[$Sector]
$value.ItalianateName = $ItalianateWindName[$Sector]

It is worth noticing that when $Sector is a Double, such in the case

PS C:\> 177/22.5
7.86666666666667

it is automatically converted to an integer when used as index for a value inside an array:

PS C:\> $WindCompassDirection[7.86666666666667]
South

For sure we have to accept here the banker's rounding mechanism, which I have already explained in a previous post.

Stay tuned for more functions from my PowerShell module to manage a pellet stove in those cold times.

Happy new year, readers, and happy coding.
Related Posts Plugin for WordPress, Blogger...