A Linux Geek Tried PowerShell — It Feels *Power*ful Indeed
Tags: automation, productivity, learning, linux
Published: 2023-05-14 10:47:19

> This article represents my personal opinion and is meant mostly for Linux (and other Unix-like) users, but feel free to join anyways ;)

> Update 2023-05-17: Changed the parameters examples formatting.

## Table of Contents
* [Preamble](#preamble)
* [The Good Stuff](#the-good-stuff)
    * [Working With Types](#working-with-types)
    * [Object-oriented Approach](#objectoriented-approach)
    * [Standard Cmdlet Names](#standard-cmdlet-names)
    * [Script and Function Parameters](#script-and-function-parameters)
    * [Full Integration With the Underlying Platform (.NET)](#full-integration-with-the-underlying-platform-net)
* [Conclusion](#conclusion)

## Preamble
I'm on the finish line to get a college degree as a Network and System Administrator. Even though I use Unix-like operating systems for more than five years and want to specialize in them — especially those based on the Linux kernel — the enterprise still runs on Windows partially. That's why even we, the Unix(-like) enthusiasts, still may have to learn it to some extent.

Less than a week ago, I was preparing for one of the last exams of the last semester. As a part of an assignment, an administrator is to create multiple AD OUs and populate them with a defined number of users. Each user is a member of a security group with a name similar to the OU's. Since the number of users for each OU is somewhat huge, up to 30, the most productive way to create them is via a script. A PowerShell script. It takes less than five minutes to write such a script but can save a lot of time and prevent headaches in the future. In a real-world scenario, this script could be placed in a version control system repository.

While I was writing the script, I learned a bit about PowerShell and liked how some things are approached. The Unix-like systems' communities would benefit from borrowing some techniques or even using the PowerShell itself, I think. Don't get me wrong, I know who's behind this project, and not trying to convince anyone to abandon their beloved Unix shells ;)

## The Good Stuff
### Working With Types
Oh boy, it is so satisfying when a shell enables you to work with types like most of the modern programming languages do. Type casting? Check. Type-specific auto-completion? Here you go. Explicit type declarations? Check.

While using this shell, one can nearly stop worrying about weird behavior when working with data of mixed types. For example, collections like arrays and lists may contain objects of one specific type or multiple types.

{% spoiler Unambiguous Types %}
A String is a String, a Double is a Double, and so on...
```console
PS /home/deathroll> [Math]::PI.GetType()

IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     True     Double                                   System.ValueType

PS /home/deathroll> $(665).GetType() 

IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     True     Int32                                    System.ValueType

PS /home/deathroll>
```
{% endspoiler %}

{% spoiler Type Casting %}
```console
PS /home/deathroll> $([Int] 14.15)          
14
PS /home/deathroll> $([String] 14).GetType()

IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     True     String                                   System.Object

PS /home/deathroll>
```
{% endspoiler %}

{% spoiler Type-Specific Auto-Completion %}
It also prints the last command containing the input string. It can be inserted by pressing the right arrow key (`→`).

![PowerShell auto-completion with type-based suggestions](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/x1rsu9x3u73rg6h9eu8f.png)

> Yes, what you see is indeed PowerShell on a Linux distribution.
{% endspoiler %}

## Object-oriented Approach
Processing complex data is not a problem anymore when you can work with types and utilize one of the most widely used programming paradigms. One can use tons of predefined types from the .NET platform or create their own.

> Everything is heavily documented:
> * [.NET API](https://learn.microsoft.com/en-us/dotnet/api/)
> * [PowerShell Documentation](https://learn.microsoft.com/en-us/powershell/)

Here's a quick example. We have created a custom class `User` that has a `Name`, an `Age`, and a list of `Interests`. Now we can create objects of this type.

```
PS /home/deathroll> class User {                                     
>>     [String] $Name                                  
>>     [Int] $Age                                                  
>>     [System.Collections.Generic.List[String]] $Interests
>> 
>>     User($Name, $Age, $Interests) { $this.Name = $Name; $this.Age = $Age; $this.Interests = $Interests }
>> }
PS /home/deathroll> [User[]] $ExampleUsers = @(                      
>>     [User]::new("Jonh Doe", 47, @("Who knows O_o")),
>>     [User]::new("Jake Smith", 22, @("Programming", "Unix-like"))
>> )
PS /home/deathroll> $($ExampleUsers[1]).Interests.Add("Writing Docs")
PS /home/deathroll> $ExampleUsers                                    

Name       Age Interests
----       --- ---------
Jonh Doe    47 {Who knows O_o}
Jake Smith  22 {Programming, Unix-like, Writing Docs}

PS /home/deathroll> $ExampleUsers[0].GetType()

IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     False    User                                     System.Object

PS /home/deathroll>
```

> Do you feel how empowering is this?

### Standard Cmdlet Names
> Cmdlets is the PowerShell's way of referring to what we know as "commands."

There's a document for PowerShell that defines how cmdlets should be named. According to it, each cmdlet consists of a `verb` and a `noun`. This makes the names easy to read, understand, and memorize. All of the predefined commands adhere to such a convention.

> Is it obvious what this command does?
> ```console
PS /home/deathroll> ConvertTo-Json -InputObject $ExampleUsers
```
> {% spoiler Output %}
> ```json
[
  {
    "Name": "Jonh Doe",
    "Age": 47,
    "Interests": [
      "Who knows O_o"
    ]
  },
  {
    "Name": "Jake Smith",
    "Age": 22,
    "Interests": [
      "Programming",
      "Unix-like",
      "Writing Docs"
    ]
  }
]
```
> {% endspoiler %}

This is just a recommendation, and people don't _have_ to follow it, but it's better to do so to be consistent.

[Approved verbs](https://learn.microsoft.com/en-us/powershell/scripting/developer/cmdlet/approved-verbs-for-windows-powershell-commands?view=powershell-7.3)

### Script and Function Parameters
> Please note that the script below may not be secure or heavily polished. Also, it's not a good idea to generate passwords in such a way, but that was part of an assignment to simplify things a bit.

Remember I mentioned a script in the [Preamble](#preamble) of this article?

Here it is:
{% gist https://gist.github.com/de4thr0ll/d347498e03efa327de9d4c42b786c8ad %}

Have you already noticed the `Param()` statement? That's how you define parameters for a script or a function. No more `$1`, `$2` and `case...esac`, my friend.

Just look at this beauty:
```
PS /home/deathroll> function Greet-User {
>>     param([Switch] $Informal, [String] $UserName)
>> 
>>     Write-Host $($(If ($Informal) { "Hey" } Else { "Hello" } ) + ", $UserName")
>> } 
PS /home/deathroll> Greet-User $env:USER      
Hello, deathroll
PS /home/deathroll> Greet-User $env:USER -Informal
Hey, deathroll
PS /home/deathroll>
```

There are multiple types of parameters that can be declared:
* `dynamic` (available only in certain circumstances)
* `static`
* `switch` (work like on and off flags)

They can have attributes like `Mandatory` (required) and so on.

And a cherry on top of this is that PowerShell reads parameters defined in a script and can perform their names auto-completion when you type them on the command line.

> This is an example for PowerShell >=7.0. It contains the [ternary](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_if?view=powershell-7.3#using-the-ternary-operator-syntax) operator.
```
PS /home/deathroll> Get-Content ./Greet.ps1 # Read the contents of the file, like the `cat` command.           
Param([Switch] $Informal, [String] $UserName)

Write-Host $($($Informal ? "Hey" : "Hello") + ", $UserName")
PS /home/deathroll> ./Greet.ps1 - # Here the TAB key was smashed :D
Informal  UserName  
PS /home/deathroll> ./Greet.ps1 -Informal $env:USER
Hey, deathroll
PS /home/deathroll>
```

### Full Integration With the Underlying Platform (.NET)
While everything aforementioned are killer features, this is a killer-killer feature.

Normally we complement shell scripts with Python, Ruby, JavaScript, or whatever scripting languages if the logic needs to be more complex. But in theory, we could just use PowerShell for all of this and tackle specific tasks by calling the .NET API where needed.  Maybe even extend the shell itself using other .NET languages like C#.

## Conclusion
PowerShell borrows some features from Unix shells: reverse and forward history search, pipes, etc., and adds tons of useful stuff. Why don't we borrow features from it or accept the technology itself where it can make our lives easier? Think about it.

Meanwhile, I'll discuss with our team if we should use PowerShell to automate things on Linux.