...
  • By Ivan Gavryliuk
  • In PowerShell
  • Posted Monday, October 3, 2016

My PowerShell Journey

I've never been a fan of command line. I still think it's just too much hassle, typing bores me, I'm a visual person. I like properly thought out fast user interfaces with keyboard shortcuts, my preferred way.

Unfortunately, we don't have many these days. A recent trend is to do everything in a browser, with JavaScript. Hey, even desktop applications are done with Electron Atom which essentially an instance of Chrome running on your computer. Per desktop application. And you wish it was just one process. For instance, the ever popular and cool Slack client for Windows, a clean instance spawns 5 processes:

And it's a bloody chat client! How many of these small desktop utilities can I afford to run on my laptop? Not many. Same goes for any "desktop" app built in Atom, they are just too hungry. Don't get me wrong, I love slack, and I do run it every day. I just hate JavaScript, or rather it's misuse and sloppy heavy apps built with it, either desktop or a browser (although I'm a certified JavaScript expert anyway).

As you already understood, my journey of exploring an alternative approach to work started with frustration of not being able to get things done in time. I've used PowerShell in the past and generally a few times a month when I absolutely had and I've decided to go on a bumpier ride.

I work a lot with Microsoft Azure and Visual Studio, and all the daily Azure Management operations are just too slow via the portal. It has improved a lot since the Classic Portal, however still slow.

PowerShell vs CMD

Windows is not known for best command line shell, I agree, it's just crap when it comes to cmd (built-in terminal). Things change with PowerShell. You get autocomplete with tab, color highlighting, aliases etc. You can even install a powershell module package manager like PsGet to get more goodness. PS is already installed on your windows PC, and you get the latest and greatest with Windows 10 (hey, you can even get it on Mac, PC or Linux if you are crazy). There is also PowerShell ISE which is sort of an IDE for writing scripts (hint - it's already installed on your Windows PC) with autocomplete, help, and all the goodness.

Most of the server management software integrates with PS with custom modules as well. You get Azure, AWS, Octopus and endless amount of other useful stuff.

PS has a real scripting language with expressions, functions, variables, loops but targeted more for speedy command line syntax which is exactly what we want.

Most of the commands in PS have aliases mapped to Linux terminal commands, you get cat, ls, cp, kill, mount etc (type Get-Alias to get the full list).

Choice of a terminal emulator

That PowerShell shortcut you get in Windows 10 is all you need.

I've ran it for quite some time. It even supports copy paste operations unlike built-in cmd where you need a weirdo combination of mouse movements to do basic stuff. Eventually when I started using PS more and more I wanted moar, like having a tabbed interface. I mean come on, I do have it in Mac OSX and Ubuntu desktop by default, not sure why Microsoft didn't include it as a standard. When I work on two git projects I don't want to switch directories all the time. Or running a long operation like deploying a cluster in Azure.

Fortunately, we have a solution here - terminal emulators. I've tried a few, however ConEmu stands out the most. Supports tabs (apparently) but much more than that! Follow the link for more features, what I found tasty for myself is:

  • Tabbed Interface. It really works, tabs are restored between restarts, along with command history.
  • Multiple shells. Kind of a nice touch, you can run multiple shells in different tabs, like good old cmd, the PS, and many more others.
  • It supports Bash out of the box. Yep, if you are a Windows 10 user with Anniversary Update you can just run bash mode in a tab next to it. Just awesome!
  • Transparency. Not a deal breaker, but a nice touch.
  • Quake Mode!* Remember Quake? . You could press "`" (tilde) which pops up a command console on top of the game screen. Same thing, but your nice tabbed console pops up in a middle of the laptop screen no matter which application you are in. I can't stress enough how useful this is when you adopt command line as a first rank citizen.

Living more comfortably with aliases

Aliases is a PS thing allowing you to shorten command names. Generally, PS commands can be very long ones like Get-AzureRmApiManagementAuthorizationServer - hell of thing to type, even with autocomplete. If you use it often you can define an alias for example shortcutting it to aus:

You can get the list of all aliases by typing Get-Alias.

Note that you cannot define aliases for commands with arguments i.e. if you need to shortcut something like git status to gs it's a wrong place to do it.

Profile Script and basic programming

If you can't do something with aliases you can always write a function! It's like bash but better (if you are a .Net developer, no offense to unix fanboys). I'll give you some examples.

Launching a Visual Studio solution

I used to have an extra toolbar in Windows 10 task bar to launch solutions I work with often. It's cool, but then I also wanted the console to open in the solution folder so I can do git and other operations. Ideally I'd like to have a terminal shortcut to do this stuff, and I wrote a PS function for this:

$devenv = "C:\Program Files (x86)\Microsoft Visual Studio 14.0\Common7\IDE\devenv.exe"

$sln = 
    @{
        "config" = "C:\dev\config\src\Config.Net.sln"
        "logmagic" = "C:\dev\logmagic\src\LogMagic.sln"
        "storage.net" = "C:\dev\storage\src\storage.sln"
        "support" = "C:\dev\support\src\Aloneguid.Support.sln"
        "edu" = "C:\dev\incubator\Education\src\Education.sln"
        "isoline-website" = "C:\dev\Isoline\PublicWebsite\src\PublicWebsite.sln"
    }

function vsLaunch([string]$projectName, [bool]$launchVs)
{
    [string]$slnFile = $sln.$projectName
    [string]$folder = Split-Path $slnFile

    if($launchVs)
    {
        Write-Output "Launching '$slnFile'"
        Start-Process -FilePath $devenv -ArgumentList $slnFile
    }

    Write-Output "Changing folder to '$folder'"
    Set-Location $folder

    Invoke-Expression "git status"
}

Then I would type vsLaunch config for example, which starts VS with a proper solution, changes current folder to the solution folder and runs git status for me as well. In order for it to be always available I can put this into so called $PROFILE script which runs every time you create a new PS console session. You can modify this script by typing notepad $PROFILE in PS, where $PROFILE is a PS variable storing the full path to this profile script. Copy-paste this function, save the document and type . $PROFILE to execute the updated profile script in current console, or just relaunch PS, now it's instantly available al the time!

Improving the script

This looks great, however after some time when $sln variable grows in size I just can't remember the project name anymore, wouldn't it be nice to have autocomplete by tab after typing vsLaunch? Yes, you can do that. The code below introduces a new command vs which does support autocomplete:

$devenv = "C:\Program Files (x86)\Microsoft Visual Studio 14.0\Common7\IDE\devenv.exe"

$sln = 
    @{
        "config" = "C:\dev\config\src\Config.Net.sln"
        "logmagic" = "C:\dev\logmagic\src\LogMagic.sln"
        "storage.net" = "C:\dev\storage\src\storage.sln"
        "support" = "C:\dev\support\src\Aloneguid.Support.sln"
        "edu" = "C:\dev\incubator\Education\src\Education.sln"
        "isoline-website" = "C:\dev\Isoline\PublicWebsite\src\PublicWebsite.sln"
    }

function vsLaunch([string]$projectName, [bool]$launchVs)
{
    [string]$slnFile = $sln.$projectName
    [string]$folder = Split-Path $slnFile

    if($launchVs)
    {
        Write-Output "Launching '$slnFile'"
        Start-Process -FilePath $devenv -ArgumentList $slnFile
    }

    Write-Output "Changing folder to '$folder'"
    Set-Location $folder

    Invoke-Expression "git status"
}

function vs
{
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $false)]
        [switch]
        $DontLaunchVs
    )

    DynamicParam
    {
        $ParameterName = "Name"

        $RuntimeParameterDictionary = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary

        $AttributeCollection = New-Object System.Collections.ObjectModel.Collection[System.Attribute]

        $ParameterAttribute = New-Object System.Management.Automation.ParameterAttribute
        $ParameterAttribute.Mandatory = $true
        $ParameterAttribute.Position = 1

        $AttributeCollection.Add($ParameterAttribute)
        $ValidateSetAttribute = New-Object System.Management.Automation.ValidateSetAttribute($sln.Keys)
        $AttributeCollection.Add($ValidateSetAttribute)

        $RuntimeParameter = New-Object System.Management.Automation.RuntimeDefinedParameter($ParameterName, [string], $AttributeCollection)
        $RuntimeParameterDictionary.Add($ParameterName, $RuntimeParameter)
        return $RuntimeParameterDictionary
    }

    begin
    {
        $projectName = $PSBoundParameters[$ParameterName]
    }

    process
    {
        vsLaunch $projectName (-Not ($DontLaunchVs.IsPresent))
    }
}

Don't ask me how it works, there is a plenty of PS scripting materials available on the internet. Same way you can create super shortcuts for other stuff you need for example gs for git status:

function gs
{
    Invoke-Expression "git status"
}

Or yolo to commit git code with a random funny message:

function yolo
{
    [Microsoft.PowerShell.Commands.HtmlWebResponseObject]$whatsit = Invoke-WebRequest -Uri "whatthecommit.com/index.txt"
    $comment = $whatsit.Content
    Write-Output "commiting with message '$comment'"
    Invoke-Expression "git commit -m""$comment"""
}

Or, for instance I need to list my virtual machines in Azure and get IP addresses, both internal and external, and public DNS entry, so I wrote a simple

function MyGet-AzVm
{
    $result = @()
    $vms = Get-AzureRmVM
    $nics = Get-AzureRmNetworkInterface
    $pips = Get-AzureRmPublicIpAddress

    foreach($vmItem in $vms)
    {
        [Microsoft.Azure.Commands.Compute.Models.PSVirtualMachine]$vm = $vmItem

        [Microsoft.Azure.Commands.Network.Models.PSNetworkInterface]$nic = $nics | where { $vm.NetworkInterfaceIDs[0] -eq $_.Id } | select -First 1

        [Microsoft.Azure.Commands.Network.Models.PSNetworkInterfaceIPConfiguration]$ipConf = $nic.IpConfigurations[0]

        [string]$pipId = $ipConf.PublicIpAddress.Id

        [Microsoft.Azure.Commands.Network.Models.PSPublicIpAddress]$pip = $pips | where { $pipId -eq $_.Id} | select -First 1

        $r = 
        @{
            "Name" = $vm.Name
            "Loc" = $vm.Location
            "RG" = $vm.ResourceGroupName
            #"?" = $vm.StatusCode
            "IP" = $ipConf.PrivateIpAddress
            "PIP" = $pip.IpAddress
            "DNS" = $pip.DnsSettings.Fqdn
        }

        $result += New-Object -TypeName PSObject -Property $r
    }

    $result
}

The possibilities are endless. Did I mention that PS is based on .NET Framework, and you can use any class from the full .NET in your PS script as well? It's a C# developer dream script languge!

Choosing the IDE

This is a personal choice but when you get into the PS beauty you probably want something more than Notepad. I've seen people using Visual Studio but I think it's an overkill for small scrips. PowerShell ISE is built-in but I didn't find it as good as Visual Studio Code with Powershell extension installed. The extension is created and maintained by Microsoft and is of an excellent quality, syntax highlighting, code navigation, IntelliSense is there. Also you get the full blown debugger experience which I honestly didn't expect from a scripting language, that totally blown me away!

Git

I never liked Git, I think it's horribly overcomplicated and is always in the way. There is always someone in any team to screw up git repo badly causing the rest to suffer. Best practices are rubbish when software is crap. Visual tools just never play nicely with Git because they create an illusion that git is straightforward and obvious when it's not. I haven't seen a tool which would let me deal with git edge cases without understanding the deep boring internals. There is GitHub for Windows (written in Atom btw) which spins up cpu cooler and performs worse than sluggish on some simple operation like a commit. GitKraken needs a dedicated machine to run, TortoiseGit just confuses me and plugs into windows explorer eating resources. I think command line is the best choice when it comes to it.

With PowerShell I've installed the PostGit module which adds autocomplete on tab, current branch/status highlight and other goodness, I totally recommend it.

PoshGit is smart not to obscure your environment when not needed and only changes current prompt when you are in a folder under git source control. If you are lucky enough to work with SVN there is a similar PoshSvn module, as well as PoshHg for Mercurial.

Our Blog

Blogging is one of the oldest publishing platforms. Although we don't blog much there is an article once in a while published here.