-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA512

         "PowerShell Polish, Part 1: Perfecting Your Scripts"
                            by Colin Cogle

                  Written Saturday, June 24, 2023.
          Available online at https://colincogle.name/posh1


ABSTRACT
Here's how to take your PowerShell script and turn it into the best
version of itself.


INTRODUCTION

If you know what PowerShell is, then there's a good chance that you're a
Windows power user or a sysadmin of some sort. Moreover, if you're work-
ing with PowerShell on a daily or weekly basis, you've probably written
a really nice script. Whether you made it to put a new tool in your vir-
tual tool belt, to automate some tedious task, or just for a programming
challenge; congratulations on writing a cool script.

Now,  you might be tempted to toss it deep inside your Documents folder
and call it done.  However, if your code is like  anything ever written,
you'll  inevitably be faced  with two hurdles:   how to share your file,
and how to manage version control. With a little work up front, you can
transform  your script from something  static and unchanging  into some-
thing that you'll want to share with the entire world.

In the PowerShell world,  there are two ways  to package something  for
mass consumption: scripts and modules. While modules are a better way to
package and share multiple files,  we'll save those for another article.
Today, we're going to focus on scripts, single files that contain all of
your code.

While you can  read the article by itself, you're free to use this as a
guide to update one of your own creations. If you have a script of yours
in mind, go ahead and open it up in your favorite code editor.  (If you
are looking for one, I recommend Visual Studio Code.)


OUR EXAMPLE SCRIPT

For the purposes of this article, I'm going to be working on this little
script.  It's nothing more than a quick game of guess the number, a rite
of passage for any computer science student, somewhere in between saying
"Hello World!" and hunting a wumpus.   To illustrate some concepts as we
go along, I've tried to violate some best practices and make some garish
styling decisions that I otherwise would not do.

----------------------------------------------------------------------
   # version 1.3
   echo "Welcome to Gues the Number!"
   $x = Get-Random -min 1 6
   Do{
   $guess=Read-Host "what's your guess?"
       if ($guess -eq $x) {write-host "You win!"}
       else
       {Write-host "Wrong!"}
   } while($guess -ne $x)
----------------------------------------------------------------------
Figure 1: Our working script before making any changes.


GIT GOOD: VERSIONING AND SOURCE CONTROL

If you're following along at home,  I'm going to have you make a lot of
changes to your script as we go along. Thus, I'm bumping this up to step
number one. You'll see why.

Eventually, you (or one of your users)  will find a bug in your script,
and you'll have to fix it. The biggest problem with managing a script is
keeping track of your changes.  You might be thinking to yourself, "Why
would I ever need an old version of my script?" Well, let's assume that,
over time, you make a version 1.1,  then a version 1.2,  and then a ver-
sion 1.3 that has a bug. How do you know _when_ the bug was introduced?
Could it be something you changed recently, or could it be something you
changed last week?

This is where a version control system comes into play. The most popular
one right now is called Git. You can quickly install it on your system:

* Windows users should download and install Git for Windows.  (This
 is what I use at work.)
* macOS users have a few options, including downloading Xcode from the
 Mac App Store (which is a lot, unless you install only the command-
 line tools, but it's a pretty nice IDE, too).
* Linux users might already have it installed, but if not, something
 like `sudo apt install git` might do the trick.

Git only works on  entire folders,  so create a new folder and put your
script inside it. Once you've done that, we're going to initiate a new
repository, add your file to Git's tracking, and commit it to a version
1.3 tag.

----------------------------------------------------------------------
PS C:\> mkdir GuessTheNumber
PS C:\> mv GuessTheNumber.ps1 GuessTheNumber
PS C:\> cd GuessTheNumber
PS C:\> git init
PS C:\> git add -A
PS C:\> git commit -m "Initial commit"
PS C:\> git tag v1.3 -m "This was the first version I put into Git."
----------------------------------------------------------------------
Figure 2: Congratulations! We've initiated a new Git repository, and
         committed our file into it.

Now that we have our initial version safely tucked away in Git, we can
work to our heart's content!


Further Reading

I don't have time to go into Git in-depth here. One could write a whole
book explaining how Git works, but if you're looking for a good tutori-
al, I can personally recommend the Microsoft Developer Tools Challenge.


STYLING YOUR CODE

Our first goal is to clean up our spaghetti code and make it look pret-
ty. We won't be changing any functionality, but by making it look nicer,
it will be easier for you (or others) to read and maintain.

Everyone has or develops their own coding style. As someone who can code
in C, C++, and Java,  I tend to follow some parts of the GNU Standard C
style whenever it makes sense and whenever it makes the code the easiest
to read.  Some tips I recommend:

*  Comment your code wherever appropriate!  (If this is something that
   you're going to share with the world, consider writing your comments
   in English, as it's the lingua franca of the programming world.)
*  Give your variables useful names. If you're not declaring a loop or
   a simple three-line function, then `$x` is probably a terrible name.
*  Stick in a line break if it makes things look cleaner.
*  Indent lines when opening a new block, and
*  Put curly braces on their own line.
*  Put spaces around operators.

If you're contributing to someone else's project, look for a CONTRIBU-
TING file in the source code.  That will tell you what coding style the
project expects from all contributors.  Please follow that, instead of
what you and I think best.

Regardless,  no  matter  what,  your PowerShell  cmdlets and statements
should be properly capitalized,  matching however you see them in offi-
cial documentation.   It also goes without saying that you should check
your spelling and grammar.

----------------------------------------------------------------------
   # This is version 1.3
   echo "Welcome to Guess the Number!"

   # Pick a random number between one and six, inclusive.
   $theNumber = Get-Random -min 1 6
   Do
   {
       $guess = Read-Host "What's your guess?"
       If ($guess -eq $theNumber)
       {
           Write-Host "You win!"
       }
       Else
       {
           Write-Host "Wrong!"
       }
   } While ($guess -ne $theNumber)
----------------------------------------------------------------------
Figure 3:  Our code after fixing spelling, grammar, and poor style.
          That's so much easier to read!

Now, let's commit our changes.

----------------------------------------------------------------------
PS C:\> git commit -m "Fixed spelling, grammar, and style."
----------------------------------------------------------------------
Figure 4:  Commit your changes so you can track them later.  You should
          write better commit messages, too, but this isn't a Git best
          practices tutorial.  I'm just teaching you enough to get you
          up and running.


PSSCRIPTANALYZER;  OR, HOW TO CONFORM TO POWERSHELL'S BEST PRACTICES

Microsoft has developed an automated tool called PSScriptAnalyzer to
scan your PowerShell files for common mistakes, bad practices, and other
things you should avoid.

Figure 5: Downloading and running PSScriptAnalyzer.
----------------------------------------------------------------------
PS C:\> Install-Module PSScriptAnalyzer
PS C:\> Invoke-ScriptAnalyzer ./GuessTheNumber.ps1

RuleName : PSAvoidUsingCmdletAliases
Severity : Warning
Line     : 2
Column   : 1
Message  : 'echo' is an alias of 'Write-Output'.  Alias can introduce
           possible problems and make scripts hard to maintain. Please
           consider changing alias to its full content.
----------------------------------------------------------------------

As you can see, it complained that we used an alias in our code.  While
it's much easier to type out interactively (and something I do frequent-
ly), you should always avoid that when writing scripts.

Likewise,  we should always spell out our parameters for clarity,  and
provide default and implied ones for clarity, too.  For example, in our
call to Get-Random, we'd used `-min` when we should have used `-Minimum`
and we can improve readability by adding in the implied `-Maximum`.

Remember, _you_ might be a seasoned PowerShell expert, but that doesn't
mean the next person reading your code will be.

----------------------------------------------------------------------
   # This is version 1.3
   Write-Output "Welcome to Guess the Number!"

   # Pick a random number between one and six, inclusive.
   $theNumber = Get-Random -Minimum 1 -Maximum 6
   Do
   {
       $guess = Read-Host "What's your guess?"
       If ($guess -eq $theNumber)
       {
           Write-Host "You win!"
       }
       Else
       {
           Write-Host "Wrong!"
       }
   } While ($guess -ne $theNumber)
----------------------------------------------------------------------
Figure 6: Cleaning up some common PowerShell trouble.


----------------------------------------------------------------------
PS C:\> git commit -m "Resolved PSScriptAnalyzer's complaints."
----------------------------------------------------------------------
Figure 7: Get into the habit of making a commit whenever you complete a
         small step.


ADDING METADATA

This is where we start to move into the more PowerShell-specific things
you can do to make your script a little easier to work on.  PowerShell
has a comment block known as `PSScriptInfo` where you can specify meta-
data — that is, information about your script.

Rather than type mere comments into your editor, you can and should de-
fine your metadata in a standard way  (especially if you have dreams of
this script being in the PowerShell Gallery someday).   You can use the
`Update-ScriptFileInfo` cmdlet to add a metadata block to your script,
or you can edit most of it in your text editor:

----------------------------------------------------------------------
PS C:\> Update-ScriptFileInfo GuessTheNumber.ps1 -Author "Colin Cogle" `
         -Version 1.3.0 -Description "Play a game of guess the number."
PS C:\> Get-Content GuessTheNumber.ps1

<#PSScriptInfo
VERSION 1.0
GUID ff00ae24-9a16-4d6f-9845-ed7a7d8133b1
AUTHOR colin
COMPANYNAME
COPYRIGHT
TAGS
LICENSEURI
PROJECTURI
ICONURI
EXTERNALMODULEDEPENDENCIES
REQUIREDSCRIPTS
EXTERNALSCRIPTDEPENDENCIES
RELEASENOTES

PRIVATEDATA
#>

<#
DESCRIPTION
foo

#>

Param()
[…]
----------------------------------------------------------------------
Figure 8: If you just want to add the comment block and fill it in in
         your IDE, then you're like me; know that the only required
         parameter to make this run is `-Description`.

Let's go ahead and fill out some of these fields.  You can remove any
lines that you're not going to use or that don't apply.

----------------------------------------------------------------------
<#PSScriptInfo
VERSION 1.3.0
GUID ff00ae24-9a16-4d6f-9845-ed7a7d8133b1
AUTHOR Colin Cogle
COPYRIGHT © 2023 Colin Cogle. All Rights Reserved.
TAGS number, guessing, game, tutorial, Windows, macOS, Linux
LICENSEURI https://www.gnu.org/licenses/agpl-3.0.en.html
RELEASENOTES
This release of GuessTheNumber.ps1 contains many bug fixes:
- - - Implement PSScriptInfo and help.
- - - Code cleanup.
#>

<#
DESCRIPTION
foo

#>

Param()
[…]
----------------------------------------------------------------------
Figure 9: This looks a lot nicer now that we've filled in the ScriptInfo
         fields.  We'll take care of the rest shortly.  (Note that I
         removed the extra line breaks as a manner of preference.)

Now,  you have metadata  defined in the proper manner,  via two comment
blocks.  This data can be read by Test-ScriptFileInfo, online galleries,
or by any eyeballs  that happen to perusing  your source code.  Be sure
to update the version number and release notes whenever you decide it's
time.


GET HELP FOR `Get-Help`

How often have you looked at someone else's script or code and wondered
how to use it? Perhaps that won't be a problem with our script, but it's
like I say at work: **always** document your work.

We can use `Get-Help` to learn more about this script:
----------------------------------------------------------------------
PS C:\> Get-Help .\GuessTheNumber.ps1
GuessTheNumber.ps1
----------------------------------------------------------------------
Figure 10: The output of `Get-Help`, by default.  There isn't much to
          look at.

If you haven't already,  open up your PowerShell script and take a look
at the new blocks in your script.  The second one is where we'll be fo-
cusing our attention.   What you're looking at is PowerShell's comment-
based help system.  This block consists of several paragraphs of plain
text occasionally marked up with headings that begin with a dot. (Though
PowerShell was developed  by Microsoft,  this syntax looks  a lot like
`troff` commands from the UNIX world.)

Let's go ahead and use some of the most common keywords to help describe
our script. At the very least, `.DESCRIPTION` was filled in for you, but
we can add and edit others.  Note that we won't be using all of these.

SYNOPSIS
   A one-line summary of what your script does.
DESCRIPTION
   A longer summary of what your code does.  You can write multiple
   paragraphs.
PARAMETER InputObject
   If your script accepts command-line parameters, you should explain
   what they do. If we had a parameter named InputObject, we would     pro-
   ceed to describe it. You can specify this as many times as you need.
INPUTS and .OUTPUTS
   If your script accepts pipeline input or generates pipeline output,
   provide the data type and a description.
EXAMPLE
   This is the only structured help item. On the first line after this,
   show the command line.  Then, after a blank line, describe what that
   will do. (Ironically, I will show the example later.) You can speci-
   fy this as many times as you want.
NOTES
   Is there anything else your users should know?  For example, do they
   need to run a command before attempting to run your script?  Does it
   only support certain data types?
LINK
   You can use this (multiple times,  if you'd like) to recommend users
   read other help topics.  You can name other cmdlets, conceptual help
   topics, or specify a URL. If you specify at least one URL, the first
   one will be used for `Get-Help -Online`.

Edit the second comment block to look something like this:
----------------------------------------------------------------------
<#
SYNOPSIS
Play a game of guess the number.

DESCRIPTION
This script plays a game of guess the number. The computer will pick a
number between one and six, and you will be prompted to guess it.

EXAMPLE
PS C:\> .\GuessTheNumber.ps1

Begins a game. This script runs interactively, and it does not accept
pipeline input or parameters.

NOTES
If you wish to stop playing, press Control-C to break.

LINK
https://colincogle.name/PoSH
#>

Param()
----------------------------------------------------------------------
Figure 11: The second comment block fleshed out with some helpful infor-
          mation. Note that an empty parameter definition was automati-
          cally added.  This is required when using `PSScriptInfo`,
          comment-based help, or both.


Let's try that command again!

----------------------------------------------------------------------
PS C:\> Get-Help .\GuessTheNumber.ps1

NAME
C:\GuessTheNumber.ps1

SYNOPSIS
Play a game of guess the number.


SYNTAX
GuessTheNumber.ps1 [<CommonParameters>]


DESCRIPTION
This script plays a game of guess the number. The computer will pick a
number between one and six, and you will be prompted to guess it.


RELATED LINKS
https://colincogle.name/PoSH

REMARKS
To see the examples, type: "Get-Help GuessTheNumber.ps1 -Examples"
For more information, type: "Get-Help GuessTheNumber.ps1 -Detailed"
For technical information, type: "Get-Help GuessTheNumber.ps1 -Full"
For online help, type: "Get-Help GuessTheNumber.ps1 -Online"
----------------------------------------------------------------------
Figure 12: The output of `Get-Help`, thanks to our hard work!
          Doesn't that look so much better?

Beautiful!  If you don't see everything you typed, that's normal;  try
running `Get-Help -Full` to learn everything.  `git add`, `git commit`,
and let's wrap this up!


GOING THE DISTANCE: SIGNING YOUR CODE

We have worked hard on our script, and now, we've got something that is
ready  to share  with the world.   Authors put their name  on the cover,
artists sign or tag their work, and PowerShell scripters also have  the
option to sign their code.   While this _is_ optional,  and you can run
unsigned code perfectly well, there are many benefits to slapping a dig-
ital signature on your script:

*  Windows PowerShell or Windows SmartScreen may show a warning when
   you try to run an unsigned script.
*  In  some corporate  environments,  an administrator may have used
   InTune or Group Policy to go one step further, and modify Windows
   PowerShell's execution policy such that unsigned scripts will not
   run.
*  If anyone modifies your script, the signature will be invalid, which
   Windows will treat as **worse** than unsigned.
*  You get to look professional (and enjoy a small ego boost) by seeing
   your name attached to your script. This goes double if you're a bus-
   iness or if you code for a living.


Where To Find Magic Bits

Now, we have a problem.  Where does someone get a code signing certifi-
cate?  While  the ISRG and Let's Encrypt have effectively democratized
server certificates  by making them available  for free,  they have no
plans to do the same for other types —  and that includes code signing
certificates. (I'll complain about S/MIME certificates in another blog
post.)

You can use  a self-signed certificate  for small deployments.  You can
share your certificate with your family and friends, and build your own
little web of trust, but that is not scalable.   If someone asked me to
install a certificate  in my root store to run their script, I would do
one of two things:  either click through the unsigned code warnings, or
simply not run their script.

Businesses might run their own internal certificate authority;  Active
Directory Certificate Services is fairly popular.  If your company has
its own private CA, you could ask your IT department to take a leap of
faith and issue you a code signing certificate. Having both run the CA
and used it to sign code, that's a wonderful option for code that will
only run on company-owned computers. For sharing with a wider audience,
though, we're back to square one.

You could detach-sign your code with PGP keys or distribute hashes, and
hope your users verify everything before running it.  There's also some
chatter about doing _something_ with blockchains,  but it's going to be
years before this can even be a contender.   Ultimately, if you want to
sign your code  *and*  sign it properly so that anyone in the world can
trust your code,  then you will need to bite the bullet and pay someone
money for these magic bits that make operating systems happy.  I was
quite happy with SignMyCode.com, but you should find a vendor that's
right for you.

In my case, I made an ECDSA private key, then gave my vendor a certifi-
cate signing request and a copy of my legal identification.  Once they
verified that I'm me, I was the proud owner of a globally-trusted code
signing certificate.


How to Sign a Script; Or, How to Turn Magic Bits Into Other Magic Bits

You're going to need a Windows computer for the following step, as
Microsoft doesn't make all of Microsoft.PowerShell.Security's cmdlets —
namely, `Set-AuthenticodeSignature ` — available on other platforms.

This little code block assumes that you have your one and only code
signing certificate imported into your Personal store.

----------------------------------------------------------------------
Set-AuthenticodeSignature `
   -Certificate (Get-ChildItem Cert:\CurrentUser\My -CodeSigning)[0] `
   -IncludeChain NotRoot `
   -TimestampServer http://timestamp.digicert.com `
   -HashAlgorithm SHA256 `
   -FilePath .\GuessTheNumber.ps1
----------------------------------------------------------------------
Figure 13:  How to sign your code.  If, for some reason, you actually
           have opinions about timestamp servers, you can substitute
           your own.

If you look at your script,  you will see a long comment block at the
very end. This is the digital signature and timestamp, encoded in a por-
table format. If you right-click your script and choose Properties, you
can also see your name on the new Digital Signatures tab!


IN CONCLUSION

Congratulations on making it this far!  If you've been following along
at home, you've now taken one of your own scripts and turned it into the
best possible version of itself.  Go ahead and share it far and wide.

You might have noticed that I didn't talk much about the PowerShell
Gallery.  While you can publish and install scripts from it, it really
focuses on modules, which I'll be touching on in a future blog post.

========================================================================
"PowerShell Polish, Part 1:  Perfecting Your Scripts" by Colin Cogle is
licensed under Creative Commons Attribution-ShareAlike 4.0 International
(CC-BY-SA).

-----BEGIN PGP SIGNATURE-----

iHUEARYKAB0WIQQ7NZ6ap/Bjr/sGU4FSrfh98PoTfwUCZJo+4AAKCRBSrfh98PoT
f4W1AQDNMVQyMmcdL9mNBHmw7tp+GE1yEmYCjw+UOFL+LXMJvwD/WMi4+0Nld0Ky
7S+lwOs/YM9DhxJeTbC7LHinq8kKpQQ=
=PXO7
-----END PGP SIGNATURE-----