C# to PowerShell an Introduction

I, like most C# developers, had heard about PowerShell since it came out. I originally shunned PowerShell as ‘just a scripting language’ and assumed it could never do what I could do with a custom C# tool. Over the past year I have learned how very wrong I was. In this article I will try to highlight some information about PowerShell I found useful during my transition.

PowerShell is smart

PowerShell creates and forces you to use certain conventions. Don’t stop reading! These aren’t conventions which make your life miserable, these make your life easier.

Arguments

PowerShell handles the command line arguments for you. There is no string[] args to be sifted through and validated to ensure the user passed the right arguments in the right order. I remember spending hours on this part of a utility alone!

PowerShell handles parsing the command line input so you, the developer, only have to define what you want and start consuming those arguments immediately.

For example, this is a generic C# console application. The application expects -startPath and -outFile arguments. The error handling in the example is minimal, forgetting -startPath will remind the user to provide it, and forgetting -outFile will set a default value.

class Program
    {
        static void Main(string[] args)
        {

            string startPath = "", outFile = "";

            int startPathIndex = Array.IndexOf(args, "-startPath");
            if(startPathIndex >= 0)
            {
                startPath = args[startPathIndex + 1];
            } else {
                Console.WriteLine("Missing -startPath argument");
            }

            int outFileIndex = Array.IndexOf(args, "-outFile");
            if(outFileIndex >= 0)
            {
                outFile = args[outFileIndex + 1];
            } else {
                outFile = "C:\Temp\test.txt";
            }
        }
    }

Now lets see the same requirements on a PowerShell script named Get-Sample.ps1

[cmdletbinding()]
param(
    [Parameter(Mandatory = $true)]
    [string]$StartPath,
    [Parameter(Mandatory = $false)]
    [string]$OutFile = "C:\Temp\test.txt"
)
begin{
	Write-Host "I'm starting in $StartPath and writing to $OutFile"
}
process{}

The four lines with the param() block is all it takes in PowerShell to achieve the same effect. The Mandatory = $true informs PowerShell which command line arguments are required, while the $OutFile = "C:\Temp\test.txt" provides a default value to the argument that isn’t required.

In PowerShell the command line arguments are the variable names used within the param() block. Running the Get-Sample.ps1 script without the -StartPath or -OutFile results in PowerShell prompting for the required missing piece of data.

Sample of missing a required argument

Providing the -StartPath results in successful completion of the script.

Sample providing all required arguments

Throughout the article we have referred to them as PowerShell command line arguments to provide a familiar term for C# developers, in actuality they are referred to as Parameters.

The samples provided here have barely scratched the surface of Parameters in PowerShell. The Microsoft Docs contain a wealth of information on Parameters.

Output

“Console.WriteLine() is all I need! It writes to screen, what else is there?”

In PowerShell there is so much more! Need to write to the screen? PowerShell has that. Need to warn the user about something? PowerShell has that. Need to write an error? PowerShell has that.

Write-Host "This is a normal message"
Write-Warning -Message "This is a warning message"
Write-Error -Message "This is an error message"

These commands are just a small sampling of commands available to output content to screen, there are many more. While the output isn’t impressive it is consistent across all PowerShell, which is not something that can be said about custom C# utilities.

While that may not be very impressive the next example will help save time formatting output. How many times have you used Console.WriteLine() to format complicated output in to something legible on screen? Have you ever tried to output a Dictionary in a legible manner? How many times have you created a ToString() method to help output class information easily and consistently? If you’re like me then you cringe at the thought.

The following code shows different types of PowerShell objects, a Custom PowerShell Class, an Array and a Hash Table/Dictionary.

	Class Car
    {
        [String]$Make
        [String]$Model
        [String]$Year
        [String]$Owner
    }

    $array = "Item1","Item2","Item3"
    $dict = @{
        Key1 = "Value1"
        Key2 = "Value2"
        Key3 = "Value3"
    }
    
    $car = New-Object Car
    $car.Make = "Honda"
    $car.Model = "HRV"
    $car.Owner = "Tim Davis"
    $car.Year = "2018"

These can be output to the console in a standardized format without any custom code.

This is extremely powerful. As a developer you want to design software quickly, concisely and efficiently. PowerShell lets us do this by handling some of these mundane tasks for us.

Getting Help

I looked at some of the tools I’ve written in the past, they either didn’t provide help or used different command lines arguments such as -?, -help or /?. I feel like this is a common problem, you have an idea, you need a tool quickly so you throw together something to help automate a task, either you cannibalize code you’ve already written or you write something quickly.

PowerShell provides a standardized convention for developers to use so that it’s own internal Help system can provide details about the script you have written. These details can be provided directly in the script during development and requires only minutes to provide. I have added these details to the Get-Sample.ps1 script presented earlier.

<#
.SYNOPSIS
    Get-Sample does something with some paths

.DESCRIPTION
    I do this to automate this process.

.LINK
    https://www.binarymethod.com

.NOTES
    FileName: Get-Sample.ps1
    Author: Tim Davis
    Contact: @binarymethod
    Created: 2020-04-22

    Version - 0.0.1 - 2020-04-22

    License Info:
    MIT License
    Copyright (c) 2020 TRUESEC

    Permission is hereby granted, free of charge, to any person obtaining a copy
    of this software and associated documentation files (the "Software"), to deal
    in the Software without restriction, including without limitation the rights
    to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    copies of the Software, and to permit persons to whom the Software is
    furnished to do so, subject to the following conditions:

    The above copyright notice and this permission notice shall be included in all
    copies or substantial portions of the Software.

    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    SOFTWARE.


.EXAMPLE

#>

[cmdletbinding()]
param(
    [Parameter(Mandatory = $true, HelpMessage="Staring Path for this process")]
    [string]$StartPath,
    [Parameter(Mandatory = $false, HelpMessage="Output Path and Filename")]
    [string]$OutFile="C:\Temp\test.txt"
)
begin{
    
}
process{}

The built-in Help system is accessed with Get-Help. Adding some simple meta information to a script provides the Help system with information it needs to display useful information to the end user.

Running Get-Help .\Get-Sample.ps1 results in this output

Get-Help .\Get-Sample.ps1


NAME
    C:\Users\Tim.Davis\Projects\PowerShell\Scripts\Blogs\C# to Powershell\Get-Sample.ps1
    
SYNOPSIS
    Get-Sample does something with some paths


SYNTAX
    C:\Users\Tim.Davis\Projects\PowerShell\Scripts\Blogs\C# to Powershell\Get-Sample.ps1 [-StartPath] <String> [[-OutFile] <String>]
    [<CommonParameters>]
    

DESCRIPTION
    I do this to automate this process.


RELATED LINKS
    https://www.binarymethod.com

REMARKS
    To see the examples, type: "get-help C:\Users\Tim.Davis\Projects\Powershell\Scripts\Blogs\C# to Powershell\Get-Sample.ps1 -examples".        
    For more information, type: "get-help C:\Users\Tim.Davis\Projects\Powershell\Scripts\Blogs\C# to Powershell\Get-Sample.ps1 -detailed".       
    For technical information, type: "get-help C:\Users\Tim.Davis\Projects\Powershell\Scripts\Blogs\C# to Powershell\Get-Sample.ps1 -full".      
    For online help, type: "get-help C:\Users\Tim.Davis\Projects\Powershell\Scripts\Blogs\C# to Powershell\Get-Sample.ps1 -online"

More information can be provided by passing -full parameter

Get-Help .\Get-Sample.ps1 -Detailed


NAME
    C:\Users\Tim.Davis\Projects\Powershell\Scripts\Blogs\C# to Powershell\Get-Sample.ps1
    
SYNOPSIS
    Get-Sample does something with some paths


SYNTAX
    C:\Users\Tim.Davis\Projects\Powershell\Scripts\Blogs\C# to Powershell\Get-Sample.ps1 [-StartPath] <String> [[-OutFile] <String>]
    [<CommonParameters>]


DESCRIPTION
    I do this to automate this process.


PARAMETERS
    -StartPath <String>

    -OutFile <String>
        
    <CommonParameters>
        This cmdlet supports the common parameters: Verbose, Debug,
        ErrorAction, ErrorVariable, WarningAction, WarningVariable,
        OutBuffer, PipelineVariable, and OutVariable. For more information, see
        about_CommonParameters (https:/go.microsoft.com/fwlink/?LinkID=113216). 

    -------------------------- EXAMPLE 1 --------------------------

    PS C:\>



    
    

REMARKS
    To see the examples, type: "get-help C:\Users\Tim.Davis\Projects\Powershell\Scripts\Blogs\C# to Powershell\Get-Sample.ps1 -examples".        
    For more information, type: "get-help C:\Users\Tim.Davis\Projects\Powershell\Scripts\Blogs\C# to Powershell\Get-Sample.ps1 -detailed".       
    For technical information, type: "get-help C:\Users\Tim.Davis\Projects\Powershell\Scripts\Blogs\C# to Powershell\Get-Sample.ps1 -full".      
    For online help, type: "get-help C:\Users\Tim.Davis\Projects\Powershell\Scripts\Blogs\C# to Powershell\Get-Sample.ps1 -online"

Conventions are good!

These are good conventions! Microsoft has designed them to make our life as developers easy and provide a usable set of standards.

PowerShell was designed to help automate IT tasks, and does it very well! PowerShell handles those mundane tasks that we as developers absolutely loathe. This article isn’t meant as a course on PowerShell, so I challenge you to start learning more and using it.


By Tim Davis

I spent my career before Truesec ensuring small financial institutions could meet the demands of todays security standards. I realized that exposure was just the tip of the iceberg and the world of IT security had much more to teach me. I love troubleshooting, finding problems, and being able to bring them to a resolution.

My true passion is programming. I spend my free time writing software for Eve Online and supporting existing deployments. I love learning new programming languages and seeing how I can use it at Truesec.