Monday, February 20, 2017

Ramp up your PowerShell knowledge in 2017 with these books

For once I am going to write a blog post which is not focused on a technical subject, and for a reason. As far as I have been able to observe, there is still a great amount of IT administrators who aren't using Windows PowerShell as their main tool for server administration even do Microsoft has been pushing it with all of its strength. Sometimes the reason is that IT guys simply lack of time. Sometimes they can't find motivation from the surrounding environment, because procedures and tools are already in place. Sometimes they are scared by the extent of the change that comes from switching away from a GUI-based server administration.

TIME TO GET A GOOD BOOK

What I think, is that often the best starting point is a good book to take on the challenge, like the one of learning PowerShell: a good book helps you to move your first steps with the language, and then to get a solid understanding of its capabilities. It will teach you how it works, how it binds to the operating system and how you can best benefit from its usage. And it will keep your motivation high by giving real world examples that you can start using straight away.


Now, if you look, say, on Amazon, you'll find tons of PowerShell books: 662 items on 56 pages, and if you're a novice, you might very well get stuck here because you don't know the authors and you can't make a choice of what book is the best for you.

That's why I have decided to write a post to mention the books that in 2017 will be a must for the modern system administrator. Most of them are still work-in-progress, but will be achieved in 2017 and will definitively be worth their price not only because they will be focused on the most recent PowerShell version (5/5.1), but also because their authors are well-renowned book writers and conference speakers that know not just how to teach a technical subject, but also how to stimulate you interest in the language. What's more, some of them, through their initiatives, have been the real engine behind the widespread of PowerShell as the vital skill to have in 2017: I am thinking for instance to Bruce Payette (principal author of the language) and Don Jones (founder of PowerShell.org) just to name two.

Let's now have a look at this list of books.

WINDOWS POWERSHELL IN ACTION

With 14 out of 19 chapters already available and planned to be published in April 2017, the must-have book in 2017 is the one by Bruce Payette and fellow MVP Richard Siddaway titled Windows PowerShell in Action, Third Edition (ISBN 9781633430297 - $59 for the printed version) which Manning makes available through the MEAP program. This is program that allows buyers to have access to the book chapters as soon as they are ready, so that their content is not locked up by a long writing and publishing process.


This book, which in its second edition already boasted 983 pages, has the advantage of being the most complete book around on this subject. This third edition covers massively some pretty hot topics such as PowerShell Classes, Workflows and Desired State Configuration (aka DSC) and should therefore be used by the system administrator who already uses PowerShell and who wants to build a rock-solid knowledge of the language. Furthermore, being Bruce Payette a founder of the language, you will get an insight of many design decisions that the PowerShell Team has had to make.

Here's a sample screenshot from the free sample of this book, just to show you the level of detail in its first pages:


It's also worth noticing that Manning did the smart move of providing a forum where you can give feedback on the content on the book as it's being written. You can access it here.

THE POWERSHELL SCRIPTING AND TOOLMAKING BOOK

The second must-have book is for sure the one by Don Jones and Jeffery Hicks titled The PowerShell Scripting and Toolmaking Book. We already know Don Jones for being the co-founder of PowerShell.org and the author of the blockbuster Learn PowerShell in a month of lunches. Jeffery Hicks is also a well-known author of PowerShell books (whose listing you can find on his blog).


As you can see when you follow the link above, they have chosen LeanPub to publish their book. LeanPub is a platform that allows technical authors to ship chapters in a Agile-manner, like you would on a blog, similarly to the MEAP program we presented above.

For the moment, their book is 80% complete and they set a target selling price at $60, but you can freely decide to pay it anything between $40 and $120. The price could seem a bit high, especially if compared to the book by Bruce Payette and Richard Siddaway, but you can be assured of two things: the first one is that Don Jones and Jeffery Hicks are excellent authors that know how to teach a subject. The second one, is that their books comes as a 'forever edition', meaning that all the updates that the authors will make to the content in the future are included in that price.

Concerning the content, the book is going to provide you with in-depth information on how to build advanced functions that include professional-grade parameter management, error handling, and built-in help, like real cmdlets. Other hot topics in this book are Unit Testing with Pester, Source Control, PowerShell ClassesPowerShell Script Analyzer and Just Enough Administration (aka JEA), just to mention a few. It will also teach you other interesting things such as how to publish to the PowerShell Gallery, as you can see from the screenshot taken from the free sample:


Same as with MEAP, LeanPub gives you the possibility to give feedback to Don Jones and Jeffery Hicks on the content of the book through a specific web page which you can find here.

POWERSHELL 101 - THE NO-NONSENSE BEGINNER'S GUIDE TO POWERSHELL

The third book I want to talk about and which will be published in 2017, is the one by Mike F Robbins. Mike is a former PowerShell MVP and now a Cloud and DataCenter Management MVP. He has already co-authored some books like PowerShell Deep Dives and Windows PowerShell TFM 4th Edition. Not to mention that is the winner of the advanced category in the 2013 PowerShell Scripting Games. So he knows his subject as you can see if you follow his blog.

The title of the book is PowerShell 101 – The No-Nonsense Beginner’s Guide to PowerShell and is published through LeanPub, just like the book by Don Jones and Jeffery Hicks. For the moment the first two chapters have been published and a third one is almost ready, so I am fully confident Mike will be able to complete it by the end of 2017 with highly valuable content.


Concerning the content, my understanding is that this book is aimed at Windows administrators that want to enter the PowerShell arena but with a focus on real world scenarios: this is a key point that will make the learning process smoother for those moving their first steps away from the GUI. Here's a screenshot from the free sample of the book:

Notwithstanding the fact that this book starts with real simple examples, like the one above, doesn't mean that you won't find a lot of very good hints on how to improve your scripting skills: Mike has a reputation for being able to write real complex functions that are extremely easy to reuse and, believe me, you won't be deceived by this book that has the added values of being sold at a very low price, $11.99.

Like for the others LeanPub books, you can find the feedback page for Mike's book at this link.

If you want to embrace PowerShell and develop your automation skills, these are the books to step up your game and add value to your career in 2017. Just choose one and skyrocket your performance.

Tuesday, February 14, 2017

Using the PoshRSJob module with a PowerShell function to perform a WinRM check up

As I explained in the previous post, I have written a function to test whether the WinRM service on a remote computer is able to accept connections and effectively execute commands. It all started with the finding that in very large environments with mixed OS versions, you can only be assured of the proper functioning of WinRM by trying to execute an actual command on a remote host. Only if the execution of that command succeeds you can safely state that the whole PowerShell remoting subsystem is correctly configured end-to-end.

The WinRM host process in action on the target server

Now you probably know that you have a couple of cmdlets that can be used to test this (I am thinking to Test-WSMan and Connect-WSMan), and that you can use Invoke-Command to run a block of code on a remote computer.

What I wanted to achieve here goes a bit further. I wanted a PowerShell function capable of testing all the possible configurations in a large environment with a high execution speed.

This involves testing things as the different authentication mechanisms such as Kerberos or NTLM, as well as testing against closed TCP ports, if any.

And this involves including some kind of parallelization.

Now to make a long story short, I have split the tests I perform in a function that I called Test-PSRemoting (whose full code you can find on my GitHub) in five blocks, where each block is accessed in the function through a Switch parameter.

Just to be clear, a Switch parameter is a parameter whose value is False by default and gets set to True when it is included. Here's a neat example I wrote to show you how this kind of parameter works:

function Get-SwitchValue ([switch]$switch1, [switch]$switch2)
{
 "Switch1 is $switch1."
 "Switch2 is $switch2."   
}

Get-SwitchValue -switch2

Switch1 is False.
Switch2 is True.
So as I said, there are five regions, which are only accessed if the corresponding Switch parameter is set to True.

The first Switch, named $Ping, is for the region of code where a ping is sent to the target server using the System.Net.NetworkInformation.Ping class:


The second Switch, named $Resolve, is used to query the DNS and return the IP address of the target server. This is accomplished with Resolve-DnsName with a query type set to A, so that only the IPv4 address is returned:


The third Switch, which I named $TCPCheck, is called when you want to check that the TCP ports used by the WinRM service are open on the destination server. As you might know, there are two ports for WinRM:
  • TCP port 5985 is for HTTP traffic and is used when you don't need to authenticate the target server because you can rely on Kerberos and on the Active Directory to authenticate it for you
  • TCP port 5986 is for HTTPS traffic and is used when you can't rely on an Active Directory  domain to authenticate the target server (like when it is in a Workgroup) and therefore you require that that target server identity is confirmed by a certificate issued by a trusted CA
Now, recent PowerShell versions have a native cmdlet for checking open TCP ports which is called Test-NetConnection. This cmdlet is designed in a way that it can be used to check for the standard WinRM port 5985 in a quick manner:
Test-NetConnection srv01 -CommonTCPPort WinRM
The issue is that this cmdlet seems always trying to ping the remote server before issuing the TCP connection. Since I haven't been able to determine whether using it with the InformationLevel parameter set to Quiet suppresses the ping, I have decided to fail back to use Windows Sockets through the System.Net.Sockets class provided in .NET framework. This has the important advantage of letting me use AsyncWaitHandle to handle timeouts shorter than the one of Test-NetConnection when used against a server which is unresponsive:
Measure-Command {([System.Net.Sockets.TcpClient]::new().BeginConnect('nosrv',5985,$null,$null)).AsyncWaitHandle.WaitOne(100,$False)}

Days              : 0
Hours             : 0
Minutes           : 0
Seconds           : 0
Milliseconds      : 125
Ticks             : 1256004
TotalDays         : 1.45370833333333E-06
TotalHours        : 3.4889E-05
TotalMinutes      : 0.00209334
TotalSeconds      : 0.1256004
TotalMilliseconds : 125.6004

Measure-Command {Test-NetConnection nosrv -port 5985}
WARNING: Name resolution of nosrv failed -- Status: No such host is known

Days              : 0
Hours             : 0
Minutes           : 0
Seconds           : 2
Milliseconds      : 306
Ticks             : 23065325
TotalDays         : 2.66959780092593E-05
TotalHours        : 0.000640703472222222
TotalMinutes      : 0.0384422083333333
TotalSeconds      : 2.3065325
TotalMilliseconds : 2306.5325
As you can see, the Test-NetConnection method is twenty times slower, and like I have said, speed is one of my main requirement for the function I am writing.

The next region, $Dcom, is where I check if DCOM can be used to retrieve the operating system of the target server as well as the name of the Active Directory Domain it belongs to. Actually this is kind of an optional part of my function: there's no link between WinRM and DCOM but it can always be interesting to know if you can switch back to DCOM/RPC to query the WMI provider on the remote host. Here's how I use the New-CimSessionOption to force my request to go over the DCOM protocol:
$SessionOp = New-CimSessionOption –Protocol DCOM
Also, always having that idea of making my function go fast, and to be robust in case the remote WMI provider is for some reasons broken, I use the New-CimSession cmdlet with a OperationTimeOut parameter set to 1 seconds. Here's the block of code for the DCOM check:



Now the final block of code. This is the part where I perform the following tests:
  • Test-WSMan with a challenge-response scheme named Negotiate, that allows me to authenticate the account I am using with Kerberos and to switch back to NTLM in case it fails
  • Test-WSMan with Negotiate on port 80, which is the old TCP port used for WinRM on Windows 2003 servers (and I have still a few of them in the place I am using this function)
  • Invoke-Command with Negotiate: in this case, since the cmdlet doesn't have a Timeout parameter, I run it in a background job which I discard after two seconds
  • Test-WSMan with Kerberos authentication
  • Test-WSMan with Kerberos authentication on port 80 for servers trunning Windows 2003 as base operating system
  • Invoke-Command with Kerberos authentication
As you can easily understand, the test with Invoke-Command is the most important part of the function since it effectively tries to retrieve the list of running services on the target server over WSMan:


Now that was for the Test-PSRemoting function.

Concerning the parallelization of the execution, I have gone down a few roads: first of all I have tried to build a quick and dirty RunspacePool but soon discovered that their implementation is so developerish that it goes well beyond what a system administrator should be able to know and understand. In the end I have decided to choose the easy path and reuse a module written and maintained by fellow MVP Boe Prox which adds a layer of abstraction to the runspaces below and makes it easy to use for the classic system administrator. The name of the module is PoshRSJob and you can find it here.

To install this module just run:

Install-Module -Name PoshRSJob
Version 1.7.3.5 has the following cmdlets:

Get-Command -Module PoshRSJob

CommandType     Name                                               Version    Source
-----------     ----                                               -------    ------
Function        Get-RSJob                                          1.7.3.5    PoshRSJob
Function        Receive-RSJob                                      1.7.3.5    PoshRSJob
Function        Remove-RSJob                                       1.7.3.5    PoshRSJob
Function        Start-RSJob                                        1.7.3.5    PoshRSJob
Function        Stop-RSJob                                         1.7.3.5    PoshRSJob
Function        Wait-RSJob                                         1.7.3.5    PoshRSJob
These cmdlets can be used in a oneliner fashion, just by piping Start-RSJob into Wait-RSJob and then into Receive-RSJob:

Start-RSJob -InputObject ('1/1/2017','2/2/2017') -ScriptBlock { Get-Date $_ } | Wait-RSJob | Receive-RSJob

Sunday, January 1, 2017 12:00:00 AM
Thursday, February 2, 2017 12:00:00 AM
Before you use my function, it is important to understand how this module accesses variables which should be available in the runspace. This is done through $Using, like in the example below, where I add one day to a given date:

$One = 1
Start-RSJob -InputObject ('1/1/2017','2/2/2017') -ScriptBlock { (Get-Date $_).AddDays($Using:One) } | Wait-RSJob | Receive-RSJob

Monday, January 2, 2017 12:00:00 AM
Friday, February 3, 2017 12:00:00 AM
It is also important to force Start-RSJob to evaluate any function you want to use in your parallel execution. This is done through the FunctionsToLoad parameter, which in my case I use to load the Test-PSRemoting function.

The last hint about this module is that you should make an heavy use of the Verbose parameter to follow whatever is happening in your runspaces and also to add a nice and useful progress bar via the ShowProgress switch inside the Wait-Job cmdlet.

So let's see now a few examples of how I use the PoshRSJob module to make the execution of my Test-PSremoting function lightning fast. In the first example I retrieve all the Windows Server names from the Active Directory, and I check whether they ping and can be reached via Invoke-Command, to return only those that actually passed this last test and save their data in a CSV file:

$Report = Start-RSJob -Throttle 20 -Verbose -InputObject ((Get-ADComputer -server dc01 -filter {(name -notlike 'win7*') -AND (OperatingSystem -Like "*Server*")} -searchbase "OU=SRV,DC=Domain,DC=Com").name) -FunctionsToLoad Test-PSRemoting -ScriptBlock {Test-PSRemoting $_ -Ping -Kerberos -Credential $using:cred -Verbose} | Wait-RSJob -Verbose -ShowProgress | Receive-RSJob -Verbose

$Report | ? Remoting_Kerberos -eq 'ok' | convertto-csv -Delimiter ',' -NoTypeInformation | Out-File C:\WinRM-after-gpo.csv
In the second example I use an ADSISearcher to query an old Windows 2003 Domain to retrieve all the Windows Servers and then I try all the different blocks of code (ping, name resolution, TCP check, NTLM, Kerberos) to return only the servers that actually responded to ping in a table:
$Servers = ((New-Object -typename ADSISearcher -ArgumentList @([ADSI]"LDAP://domain.com/dc=domain,dc=com","(&(&(sAMAccountType=805306369)(objectCategory=computer)(operatingSystem=*Server*)))")).FindAll()).properties.name

$Report = Start-RSJob -Throttle 20 -Verbose -InputObject $Servers -FunctionsToLoad Test-PSRemoting -ScriptBlock {Test-PSRemoting -Ping -Resolve -TCPCheck -DCOM -Negotiate -Kerberos $_ -Credential $using:cred -Verbose} | Wait-RSJob -Verbose -ShowProgress | Receive-RSJob -Verbose

$Report | ? ping -eq 'ok' | format-table * -AutoSize
For sure you can imagine here any kind of grouping of your results, with Group-Object, or you could print the results in a dynamic table with Out-GridView. What you do will depend on your needs.

Kudos to Boe for the PoshRSJob module. If you have any question on the function I wrote, or if you want to improve it, feel free to get in touch with me.

Tuesday, February 7, 2017

From Test-Connection to a one line long Filter to ping via PowerShell with a timeout

Having had to deal with mass activation of WinRM in large environments, I have been asked to provide a report of the servers actually answering to remote requests. Sure enough I wrote a PowerShell function for this task, which I am going to present in a future post. Today I just want to take the time to do a bit of a back-to-the-basics post to explain the first part of the function that does a pretty common job: pinging servers to see if they are alive.

PITFALLS OF TEST-CONNECTION

You must be thinking that this is easily accomplished with Test-Connection, but the problem of this cmdlet is that it can be super slow in large environments, where you probably have servers that happen to be offline, servers that don't exist anymore but are still referenced in the Active Directory or in the DNS, servers that block ICMP and servers which are just temporarily unresponsive.

The reason for not to use Test-Connection is that, believe it or not, Test-Connection does not support a -TimeOut parameter so each ping by default respects the timeout of 1000ms that was designed with networks of 20-30 years ago in mind. So for each offline computer you have by default to wait for four times 1000ms and pinging many servers can take a long time.

A little side note here: do not confuse the -TimeToLive or TTL parameter that comes with Test-Connection with the Timeout parameter we hope to have: a TTL is the maximum number of hops it can take to get from one host to another, not the time to wait for a reply. It is used to make certain a packet doesn't live forever on your network when it's lost.  The PowerShell help and a lot of other resources out there are confusing those and that's why in IPv6 TimeToLive has been renamed to Hop Limit:


So, even if Test-Connection does not have a -TimeOut parameter, there are a couple of ways to make it run a bit faster. Let's have a lot at those ways of pinging and see how and why they make the classical error handling a bit complex.

The first option to speed it up is to use it with the -Quiet switch set to True and the -Count parameter set to 1, like in the following example:

Test-Connection $Computer -Quiet -Count 1

Now, for the error handling part, you could think that including that command in a Try{}Catch{} block would work. You're wrong: using the -Quiet parameter forces Test-Connection to suppress the errors and just return a Boolean value, which is $True if any of the four pings succeeds, otherwise it is $False.

So you have to enclose the command in a conditional statement to return its outcome:

if(Test-Connection $Computer -Quiet -Count 1) { 'OK' } else { 'NOK' }

Now if you run Test-Connection without -Quiet, the cmdlet returns a Win32_PingStatus object when the query is succesful:

Test-Connection $Computer -Count 1 | Get-Member

   TypeName: System.Management.ManagementObject#root\cimv2\Win32_PingStatus

Name                           MemberType     Definition
----                           ----------     ----------
PSComputerName                 AliasProperty  PSComputerName = __SERVER
Address                        Property       string Address {get;set;}
BufferSize                     Property       uint32 BufferSize {get;set;}
NoFragmentation                Property       bool NoFragmentation {get;set;}
PrimaryAddressResolutionStatus Property       uint32 PrimaryAddressResolutionStatus
ProtocolAddress                Property       string ProtocolAddress {get;set;}

On the contrary, if the query is unsuccessful, it doesn't return anything at all and you have to use a standard Try{}Catch{} block in order to retrieve the result:

try { Test-Connection $Computer -Count 1 -ErrorAction Stop; 'OK' } catch { 'NOK' }

To sum up, sending just one echo request reduces the execution time but, still, if I can't shorten the timeout, each query will last way too long for the inventory of many servers.

Let's now have a look then at something that manages very low timeouts (down to 5 milliseconds or less).

THE WIN32_PINGSTATUS CLASS

The first possibility is through Get-WmiObject Win32_PingStatus, which supports a timeout: as you can read in the documentation:


Get-WmiObject Win32_PingStatus -filter "address='$computer' and timeout=5"

The issue with this class is that Win32_PingStatus -f "Address='Serverdoesnotexist'" doesn't lead to an error, so you have to rely on the StatusCode property to see if the ping succeeded or not:

if ((Get-WmiObject Win32_PingStatus -filter "address='$computer' and timeout=5").StatusCode -eq 0) {
    'OK'
}
else {
    'NOK'
}

THE SYSTEM.NET.NETWORKINFORMATION.PING CLASS

The other possibility is to rely on the System.Net.NetworkInformation.Ping class.

$ping = new-object System.Net.NetworkInformation.Ping 
$ping.Send($computer)

In this case, while you have to mess with the PingOptions class to add such things as a TTL or to set a DontFragment flag, you can directly use the (IPAddress^, Int32) overload to define the maximum number of milliseconds to wait for the ping to succeeded:

$ping.Send($computer,5)

As you can see I have set the TimeOut in seconds to a very low value if compared to the standard 1000ms of the classical ping command: setting the value so low will make your ping sweep go much faster while keeping a very good precision since, on modern network, servers that are alive will send an echo reply at the speed of light, even if there are a bunch of routers in between.

The difficulty with using this class is that there are three possible cases that require different error handling methods:

Case n. 1: you try to ping a server that is temporarily offline

$ping.send($offline).status

This won't return an error but will return a PingReply object:


This object will have the status set to TimedOut, so to catch this you have to use an If statement:

if($ping.send($offline).Status -eq 'Success'){'OK'}else{'NOK'}

Case n. 2: you try to ping a server that doesn't exist anymore (meaning no DNS record)

$ping.send($not_existing)

This will return an exception:

Exception calling "Send" with "1" argument(s): "An exception occurred during a Ping request."

that you can grab with Try{}Catch{}:

try {$ping.send($not_existing)}catch{'NOK'}

Case n. 3: you try to ping a server that is up and running

$ping.send($ok).Status

Since this case will return Success, using an If statement is the only option:

if($ping.send($ok).Status -eq 'Success'){'OK'}else{'NOK'}

At the end of the day, it's clear that in order to get all the possible errors, you have to enclose that command in both a Try{}Catch{} block and in a If{}() block:

try {
    $ping = new-object System.Net.NetworkInformation.Ping
    if ($ping.send($computer,5)) {
      'NOK'
    }
    else {
      'OK'
    }
  } catch {
    'NOK'
  }

SPEED COMPARISON

Now let's do a few measures to compare all the methods presented above and see what is actually the fastest way to ping in PowerShell when you try to reach a server that is temporarily offline:

$sb = { Test-Connection $offline -Quiet -Count 1 }
(Measure-Command $sb).TotalSeconds

$sb = { Get-WmiObject Win32_PingStatus -filter "address='$offline' and timeout=5" }
(Measure-Command $sb).TotalSeconds

$sb = { $ping = new-object System.Net.NetworkInformation.Ping,$ping.send($offline,5) }
(Measure-Command $sb).TotalSeconds

The first Test-Connection will last four seconds, while Win32_PingStatus will last 0,3 seconds and System.Net.NetworkInformation.Ping will last 0,1 seconds.

Once we know that System.Net.NetworkInformation.Ping is the faster method, let's build something that allows us to easy reuse it.

BUILDING A FILTER

My idea is just to use something old but still useful in some cases: a PowerShell Filter:

filter Invoke-FastPing {(New-Object System.Net.NetworkInformation.Ping).send($_,5)}

Here's three examples of how you can quickly take advantage of this Filter:

Example 1: ping two or more hosts and suppresses errors for unresponsive servers:
'srv1','srv2' | Invoke-FastPing 2>0

Example 2: ping sweep a whole subnet:
1..254| foreach { "192.168.1.$_"| invoke-fastping } | Where-Object status -eq 'success' | Format-Table * -AutoSize

Example3: ping al the servers in your Active Directory and present the results in a dynamic table:
(Get-ADComputer -filter {OperatingSystem -Like "*Server*"} -searchbase "OU=Servers,DC=MyCompany,DC=Com").dnshostname | Invoke-FastPing | Out-GridView

As you can see, just one line of code and you have a small tool to ping remote hosts with a small timeout. Great.

FUTURE IMPROVEMENTS

To end this post, let me tell you that there is an open issue (by J. Snover himself) on the necessity to add a timeout to Test-Connection:

https://github.com/PowerShell/PowerShell/issues/2478

Hope this is implemented soon.

Stay tuned for more PowerShell.

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.

Tuesday, December 27, 2016

A function for robust command execution in PowerShell

I have always been a great fan of a tool named RoboCopy, which I bet many of you have used countless times. Now these days I have been in need for this very same type of robustness for one of my functions since I am running it in an unreliable environment.

What I wanted to achieve with PowerShell in particular, was to fetch a great deal of data from public web servers and reuse the information in my scripts. Unfortunately when you use a cmdlet, be it Invoke-RestMethod, or Test-Connection, you can get failures which are not due to your cmdlets but to the underlying infrastructure (such as a Wi-Fi network, a distorted topology because of a flapping router or a way too busy web server).

Sure Invoke-RestMethod has a TimeoutSec parameter, but what if it fails and I really need the information coming from that website? Well, this reasoning brought me to write an advanced function that takes a command and its parameters and tries to run it a given number of times (three by default) with intervals of three seconds.

This function, which I called Start-RoboCommand (Start is an approved verb, so that my PSScriptAnalyzer is happy, and I borrowed the idea of the RoboCommand noun from RoboCopy itself), also supports an improved functioning where the command is run indefinitely, through the addition of a -Wait parameter, such as the one you can find in recent versions of Get-Content.

To finish with, I added a LogFile parameter to log errors (which is particularly important here because we are exactly dealing with commands not being successfull) and Verbose, which tells you exactly what's going wrong.


Now without further ado, here's my function:

function Start-RoboCommand {

<#
.Synopsis
   Function that tries to run a command until it succeeds or forever
.DESCRIPTION
   Function that tries to run a command until it succeeds or forever. By default this function tries to run a command three times with three seconds intervals.
.PARAMETER Command
    Command to execute
.PARAMETER Args
    Arguments to pass to the command
.PARAMETER Count
    Number of tries before throwing an error
.PARAMETER Wait
    Run the command forever even if it succeeds
.PARAMETER Delay
    Time in seconds between two tries
.PARAMETER LogFile
    The path to the error log
.EXAMPLE
   Start-RoboCommand -Command 'Invoke-RestMethod' -Args @{ URI = "http://guid.it/json"; TimeoutSec = 1 } -Count 2 -Verbose
.EXAMPLE
   Start-RoboCommand -Command 'Invoke-RestMethod' -Args @{ URI = "http://notexisting.it/json"; TimeoutSec = 1 } -Count 2 -Verbose
.EXAMPLE
   Start-RoboCommand -Command 'Invoke-RestMethod' -Args @{ URI = "http://guid.it/json"; TimeoutSec = 1 } -Wait -Verbose
.EXAMPLE
   Start-RoboCommand -Command 'Invoke-RestMethod' -Args @{ URI = "http://notexisting.it/json"; TimeoutSec = 1 } -Wait -Verbose
.EXAMPLE
   Start-RoboCommand -Command 'Test-Connection' -Args @{ ComputerName = "bing.it" } -Wait -Verbose
.EXAMPLE
   Start-RoboCommand -Command 'Test-Connection' -Args @{ ComputerName = "nocomputer" } -Wait -LogFile $Env:temp\error.log -Verbose
.EXAMPLE
   Start-RoboCommand -Command Get-Content -Args @{path='d:\inputfile.txt'} -Wait -DelaySec 2 -LogFile $Env:temp\error.log -Verbose
.NOTES
   happysysadm.com
   @sysadm2010
#>

    [CmdletBinding(SupportsShouldProcess,DefaultParameterSetName='Limited')]
    Param (
    
    [Parameter(Mandatory=$true)]
    [Alias("Cmd")]
    [string]$Command, 

    [Parameter(Mandatory=$true)]
    [hashtable]$Args, 

    [Parameter(Mandatory=$false,ParameterSetName = 'Limited')]
    [int32]$Count = 3, 

    [Parameter(Mandatory=$false,ParameterSetName = 'Forever')]
    [switch]$Wait,

    [Parameter(Mandatory=$false)]
    [int32]$DelaySec = 3,

    [Parameter(Mandatory=$false)]
    $LogFile
    )
    
    $Args.ErrorAction = "Stop"
        
    $RetryCount = 0

    $Success = $false
    
    do {

        try {

            & $command @args

            Write-Verbose "$(Get-Date) - Command $Command with arguments `"$($Args.values[0])`" succeeded."

            if(!$Wait) {
                
                $Success = $true

                }
            
            }
        
        catch {

            if($LogFile) {

                "$(Get-Date) - Error: $($_.Exception.Message) - Command: $Command - Arguments: $($Args.values[0])" | Out-File $LogFile -Append

                }
            
            if ($retrycount -ge $Count) {

                Write-Verbose "$(Get-Date) - Command $Command with arguments `"$($Args.values[0])`" failed $RetryCount times. Exiting."

                $PSCmdlet.ThrowTerminatingError($_)
                
                }

            else {

                Write-Verbose "$(Get-Date) - Command $Command with arguments `"$($Args.values[0])`" failed. Retrying in $DelaySec seconds."

                Start-Sleep -Seconds $DelaySec

                if(!$Wait) {
                
                    $RetryCount++

                    }

                }

            }

        }

    while (!$Success)

 }

Let me know how it works for you and if you have any suggestion on the logic I'll be more than happy to improve it over time. Fo sure you can find it also on my github.

Thursday, December 8, 2016

Spotlight on the PSReadline PowerShell module

The trend is clear: Microsoft has shifted some major projects, like .NET and PowerShell itself, into the open-source ecosystem, and has made them cross-platform. Today you can run your PowerShell scripts on a GUI-less Windows Server Core, or on a headless Nano Server, but also on Linux, and on a Mac.

There is a project in particular which reveals this kind of cross-pollination between OSes, and it is the PSReadline module, which is aimed at bringing the GNU Readline experience to your PowerShell console.
This module is installed by default on Windows 10 and brings some slick functionalities which are well worth a quick look.
The first functionality is the fact that with PSReadline, the console preserves command history across sessions. Ok, you were used to Get-History to find the list of the typed commands, and to use Invoke-History (aliased as 'r') to run commands found in the history. But these two cmdlets are limited to the current session:

Now with the arrival of PSReadline, which is loaded by default when you start a PowerShell console, you got the possibility to retrieve commands typed in previous sessions, even across reboots. This is achieved through log files stored inside the Application Data folder:
  • $env:APPDATA\Microsoft\Windows\PowerShell\PSReadline\ConsoleHost_history.txt for the PowerShell console host (conhost.exe)
  • $env:APPDATA\Microsoft\Windows\PowerShell\PSReadline\Windows PowerShell ISE Host_history.txt for the Integrated Scripting Environment (ISE)
  • $env:APPDATA\Microsoft\Windows\PowerShell\PSReadline\Visual Studio Code Host_history.txt for Visual Studio Code, the new toy for those into DevOps
How I discovered that? Simple. The PSReadline module comes with five cmdlets:
  • Get-PSReadlineKeyHandler: gets the key bindings for the PSReadline module
  • Get-PSReadlineOption: gets values for the options that can be configured
  • Remove-PSReadlineKeyHandler: removes a key binding
  • Set-PSReadlineKeyHandler: binds keys to user-defined or PSReadline-provided key handlers
  • Set-PSReadlineOption: customizes the behavior of command line editing in PSReadline.
If you issue “(Get-PSReadlineOption).HistorySavePath” you will get the location where the system keeps the command history for your current interpreter.

Now for some reason, the only working log between those listed above is the one for PowerShell on the command line, probably because PowerShell ISE and VSCode don't have a true console (conhost.exe) behind it:


Being the Application Data folder user-specific, you only have access to the command history for your user account: so there is one ConsoleHost_history.txt file for each user on a given computer. The permissions are set in a way that the admin can access the command history for other users, which is good for checking your systems.

Here's a script I wrote to retrieve a list of all the consolehost_history.txt files on my systems, so that I know who used PowerShell and when:
(Get-ChildItem -Path c:\users).name | % {

     Get-Item ((Get-PSReadlineOption).HistorySavePath -replace ($env:USERNAME,$_)) -ErrorAction SilentlyContinue

     } | Select-Object FullName,

                       CreationTime,

                       LastWritetime,

                       @{Name="Kbytes";Expression={ "{0:N0}" -f ($_.Length / 1Kb) }},

                       @{Name="Lines";Expression={(Get-Content $_.fullname | Measure-Object -Line).Lines}}
To prevent PowerShell from logging any command just type:
Set-PSReadlineOption –HistorySaveStyle SaveNothing
Other interesting settings that you could adopt or make custom are:
Set-PSReadLineOption -HistoryNoDuplicates
and
Set-PSReadLineOption -MaximumHistoryCount 40960
I wouldn't bother changing the HistorySaveStyle because the default parameter seems well suited to me: SaveIncrementally means that every run command is stored in the log before being actually executed.

If you want to erase you command history, you can just press ALT+F7, as you can discover by issuing:
Get-PSReadlineKeyHandler | ? Function -eq 'clearhistory'

Key    Function     Description
---    --------     -----------
Alt+F7 ClearHistory Remove all items from the command line history (not PowerShell history)
The second functionality is the possibility to access and search the history log in an interactive way. What I mean is that you can use your keyboard to search the history by pressing combinations of keys. The discovery of the existing keys is performed with:
Get-PSReadlineKeyHandler | ? function -like '*history*'

Key       Function                Description
---       --------                -----------
UpArrow   PreviousHistory         Replace the input with the previous item in the history
DownArrow NextHistory             Replace the input with the next item in the history
Ctrl+r    ReverseSearchHistory    Search history backwards interactively
Ctrl+s    ForwardSearchHistory    Search history forward interactively
Alt+F7    ClearHistory            Remove all items from the command line history (not PowerShell history)
F8        HistorySearchBackward   Search for the previous item in the history that starts with the current input - like PreviousHistory if the input is empty
Shift+F8  HistorySearchForward    Search for the next item in the history that starts with the current input - like NextHistory if the input is empty
Unbound   ViSearchHistoryBackward Starts a new seach backward in the history.
Unbound   BeginningOfHistory      Move to the first item in the history
Unbound   EndOfHistory            Move to the last item (the current input) in the history
As you can see pressing Ctrl+r will bring up a bottom-top search (identified by bck-i-search), and just start typing and PSReadline will complete the lines with commands from the history logfile:


The third functionality is the fact that PSReadLine allows you to mark, copy, and paste text in the common Windows way. It is actually just like if  you were in Word: CTRL+C copies text, CTRL+X cuts text, and CTRL+V pastes the text. CTRL+C can still be used to abort a command line, but when you select some text, with the CTRL+SHIFT+ArrowKeys key combination for instance, PSReadline will switch to CTRL+C Windows mode. Awesome.

The fourth functionality is syntax checking as you type. When PSReadline detects a syntax error it turns the grater-than-sign on the left to red, like in the following example where I forgot to close the double quotes after the $Computer variable:


If to all these functionalities you add the syntax coloring provided by PSReadline, or also the possibility to use key combinations like CTRL+Z to undo code changes, then there you are with a PowerShell console that is a delight to use. And that you can even install on your old Windows 7 by installing WMF v5 and then running the following line of code to get the module from the PowerShell Gallery:
Install-Module -Name PSReadline
Now just choose your way. Here's a comparative screenshot of the four main development environment I use:


Happy coding.
Related Posts Plugin for WordPress, Blogger...