Tips and Tricks from PowerShell Core Validation

It has been a privilege for the CAT team to work with customers and the PowerShell team to validate early builds and experiences with PowerShell Core. Some of the customers involved were key influences on the whitepaper, The Release Pipeline Model Applied to Windows Server and Microsoft Cloud. As a result, validation has included many experiences from outside the traditional Microsoft tool chain, such as Vagrant and Jenkins.

For this blog post, I wanted to share some of the learnings that I gained during the validation experience. This is not meant to be a complete picture of PowerShell Core. Rather, a glimpse at some exciting new possibilities.

Voyage of Discovery

In my mind the best thing about PowerShell as a scripting language is the never ending journey of exploration. When I have the need to accomplish a task and I’m unfamiliar with the area of technology, I rely on Get-Command. If I need to gather information I start the learning process by looking for commands that begin with the verb Get, so I open the console and type Get-Command Get-*. When I need to create something new, I’m going to be looking for commands that start with the verb New. When I need to change a value, I am probably looking for commands that start with Set. If I plan to run something, the commands probably start with Invoke, and so forth.

I view myself as a novice with Linux based servers. I am certainly not an expert but I am by no means a rookie either. Still, it is a muscle I don’t regularly exercise, so to accomplish even the most routine tasks I require a search engine. My testing began by connecting to an Ubuntu 14.04 server running on Azure using SSH. I followed the documented steps from the PowerShell repo to install .NET core and PowerShell Core. Then just as I would when remotely connected to Windows Server Core or Nano, I executed Get-Command to start looking at which cmdlets are available and how I could combine them to perform operations wizardry.

The “ah ha!” moment for me has been combining PowerShell Core with the existing Linux tools and scripting languages. Now the same work patterns that I describe above are applicable on platforms where my domain knowledge has less depth, but I am exploring topics using a familiar approach.

A simple proof of concept

I wanted to experiment with simple “operations tasks” that I handle daily on Windows based machines. The bare bones list that came to mind was:

  • getting the IPv4 address of the machine
  • finding out which DNS name servers it is pointing to
  • checking the drive configuration

I purposefully chose these areas because (at the time of this writing) there are not yet cmdlets available. The challenge would be understanding how to weave together the cmdlets at my disposal until the tasks become simple.

Getting text using Select-String

Of the three challenges, the most simple solution was checking which DNS name servers the machine is pointing to. This also allows me to demonstrate a core concept that will be an important skill, getting text.

The existing tools and scripting languages on Linux are GREAT at working with text output. That is a fundamental skill. These tools, such as the cat command, could simply be ‘wrapped’ by PowerShell functions if needed. I wanted to be stubborn and try working with the native PowerShell cmdlets for pulling information out of text. The Select-String cmdlet has helped a lot in this pursuit.

If you are not familiar with Select-String, try the command Get-Help Select-String -detailed from the console. This will provide you with more information. Get-Help is a handy way to learn more about cmdlets as you are experimenting.

get-content /etc/resolv.conf | select-string nameserver

nameserver 10.0.0.5

In this example, the one-line command is just capturing the contents of the file resolv.conf, and then returning each line that includes the work ‘nameserver’. Of course it will not always be so simple. You might need to capture multiple lines of text and extract specific values. A good trick to know is the -Context parameter for Select-String. The value will be the number of lines before and after the text you are selecting that you want to have returned. Seperate the values with a comma. So to return any line with the word ‘nameserver’ and the line after it, see the example below.

get-content /etc/resolv.conf | select-string nameserver -context 0,1

> nameserver 10.0.0.5
  search dns.example.com

Getting text using Regular Expressions

As I mentioned above, there are Linux tools to handle this. Great tools, in fact, such as grep, awk, and sed. The challenge I gave myself was to use the style and approach to working with regular expressions that I am most familiar with from PowerShell script examples. Including both the -match operator and the .NET method Regex.Matches.

See also:

The task of getting the IPv4 address of a server provides a nice example. It is also an opportunity to work with text that is output from a command.

$ipInfo = ifconfig | select-string 'inet'
$ipInfo = [regex]::matches($ipInfo,"addr:\b(?:\d{1,3}\.){3}\d{1,3}\b") | ForEach-Object value
$ipInfo

addr:10.0.0.2
addr:127.0.0.1

Although that is the output I was looking for, it would be nice to clean up the “addr:” text in front of each address. If you are unfamiliar with manipulating strings using PowerShell, pass the result of Select-String to Get-Member using a pipeline operator |.

$ipInfo | Get-Member

Among the available options are split and replace.

$ipInfo.replace('addr:','')

10.0.0.2
127.0.0.1

Finally, I don’t really need to see the localhost address in every output, so I can apply a filter using the Where-Object cmdlet.

$ipInfo.replace('addr:','') | Where-Object {$_ -ne '127.0.0.1'}

10.0.0.2

To take this work and create a nice function that we can re-use in the future.

function Get-IPAddress {
    $ipInfo = ifconfig | select-string 'inet'
    $ipInfo = [regex]::matches($ipInfo,"addr:\b(?:\d{1,3}\.){3}\d{1,3}\b") | ForEach-Object value
    $ipInfo.replace('addr:','') | Where-Object {$_ -ne '127.0.0.1'}
}

Get-IPAddress

10.0.0.2

Objects

All this working with text is unfamiliar to experienced PowerShell users. A core concept in PowerShell is working with Objects. The last example, getting a list of available disks, provided an opportunity to try this. Also, since getting disk information is a privileged operation, I got a chance to try the sudo command within a function in PowerShell Core.

Piling on the ideas we have explored so far, I need to find the command that will provide text output with disk information, and I will need to manipulate that text. The new concept this example introduces is taking that text and putting it in an object using the New-Object cmdlet. The text string values are simply passed in to the -Property parameter as a hashtable.

Note the use of square brackets to index in to an array. For example, $array[0] returns just the first value.

Function Get-DiskInfo {
    $disks = sudo parted -l | Select-String "Disk /dev/sd*" -Context 1,0
    $diskinfo = @()
    foreach ($disk in $disks) {
        $diskline1 = $disk.ToString().Split("`n")[0].ToString().Replace('  Model: ','')
        $diskline2 = $disk.ToString().Split("`n")[1].ToString().Replace('> Disk ','')
        $i = New-Object psobject -Property @{'Friendly Name' = $diskline1; Device=$diskline2.Split(': ')[0]; 'Total Size'=$diskline2.Split(':')[1]}
        $diskinfo += $i
    }
    $diskinfo
}

Get-DiskInfo
[sudo] password for psuser:

Friendly Name            Total Size Device
-------------            ---------- ------
Msft Virtual Disk (scsi)  31.5GB    /dev/sda
Msft Virtual Disk (scsi)  145GB     /dev/sdb

Now you can work with specific properties of the object. Recall you can always pipe the object to Get-Member to see which properties and methods are available.

(Get-DiskInfo).Device
/dev/sda
/dev/sdb

Performing the same work across platforms

For the final challenge, is it now possible to author a function in PowerShell that can be run across Windows, Linux, and macOS? There is a simple solution that makes this very straightforward.

PowerShell Core includes variables:

  • $IsLinux
  • $IsOSX
  • $IsWindows

So in your function you can perform Linux type work in one IF block and Windows type work in another, and as long as you return an object with the same properties you can work with the results using the same cmdlets. In testing I found it handy to check if I was in Linux or OSX and if not, use cmdlets from modules I expected to find in Windows. Since the $IsWindows variable is not available (at the time of this writing) in the version of PowerShell provided with Windows.

function Get-Something {
    if ($IsLinux -or $IsOSX) {
        # Get this information using approaches for Linux and/or OSX
        $something = get-content /file | select-string text
        }
    else {
        # Get this information using cmdlets already available in Windows
        $something = get-whatever -parameter value
    }

    $something | Where-Object {$_ -ne 'excluded value'}
}

For more information

For more information on this topic and for expansion on the examples used above, see ‘Learning PowerShell’ in the docs folder of the PowerShell repo. While you are there, you might also visit the ‘demos’ folder at the root of the repository for the latest examples of using PowerShell in cross-platform environments.

Thank you!
Michael Greene
Principal Program Manager
ECG CAT