I sat down to learn enough PowerShell to recreate one of my bash functions.

What have I learned so far?
PowerShell prefers long names, and it tries to make them generic. Verb-Noun, do what to what resource.
Get-Item, Set-Location.

The PowerShell glossary defines "noun" as "The word that follows the hyphen in a Windows PowerShell cmdlet name." https://docs.microsoft.com/en-us/powershell/scripting/learn/windows-powershell-glossary?view=powershell-6
I like long names for readability of scripts (when trailblazing).

Short names are better for exploring.
In bash (and other Unix shells), programs compose on STDIN and STDOUT. The interface between them is strings of text.

In PowerShell, the interface is objects. Programs (cmdlets) pipe objects to each other.

Ah, rich data! Ah, no string parsing!
PowerShell provides each cmdlet with standard

- command-line parsing
- help, triggered by -?
- definitions of some common parameters
- output formatting
- error handling options (coooool)
The common parameter -WhatIf is a great idea! It's like --dry-run in some Unix utilities like make.

Separate making decisions from implementing them.

You can even turn it on for the whole session with `$WhatIfPreference=$true`
Then there's the very cool -ErrorAction common parameter.
Set it to "SilentlyContinue" when you don't want errors printed to stderr.

For instance, test whether a directory exists:
if (Get-Location -Path $ofInterest -ErrorAction SilentlyContinue) { … }
Output formatting: by default you get some reasonable printout of the object. Or, pipe the output to a formatting cmdlet, like
Format-Table -Property name,value,etc -wrap
Out-Host -paging (like piping to 'less')
Get-Member (like reflection -- see methods & properties)
I got all excited about this and then had a very hard time figuring out how to make a cmdlet at all.

So far:
- make a file with extension .ps1
- create a function in there, and then make it an "advanced function" by adding cmdlet support
- source this file: . ./whatever.ps1
but watch out - you need the cmdlet spell and then params(). You'll get an error about CmdletBinding "Unexpected attribute"
until you add params() AFTER it.
Here's a weird thing: any command that you call in your function, if it returns something and you don't assign that to a variable or pipe it somewhere, that becomes an object that is output from your script.
Also any value that you put on a line by itself.
PowerShell has a distinction between data output, which continues through the pipeline, and output to the user.

`Write-Host` sends a message to the person running the cmdlet. Something on a line by itself goes down the pipe.
Checking the exit code of a command is different than in bash. Use $LastExitCode, and test with bash-like comparison operators.

This pops a dialog if "git push" fails:
Windows is generally case-insensitive. Comparison operators like "-eq" are case-insensitive with strings.

There are case-sensitive versions that add a C.
"Yes" -eq "yes"
but NOT
"Yes -ceq "yes"

I guess this is a benefit of using short names instead of symbols for the operators.
like unix tee but even more useful: Tee-Object.
Use it to send output to multiple places. The extra one can be a file or a variable.
oh, and PowerShell for newline in strings is


(backtick n)
Instead of \n
My function (and the alias to it) automatically accept positional parameters in the order I declared them, or named parameters in any order.
Cool! Did not expect!

I love happy surprises like this. It's like Ruby, except easier to install.
Now I want to change the title of the terminal tab.

echo -ne "\033]0;Changed Title\007"

$host.ui.RawUI.WindowTitle = “Changed Title”
ooh VS Code has a snippet for cmdlets --
Type "cmdlet" and ctrl-space. It gives me a whole template for the cmdlet!
I finally figured out how to map the objects in the pipe.
It's foreach, then a block in curlies and $_ is the current object.

Get-ChildItem | foreach { $_.name }
OK, here's something weird. I wanted to get the error message from 'git push' and save it, so that I can display it in my popup dialog.

So, pipe stderr to stdout (like in unix), then tee it to a variable

git push 2>&1 | Tee-Object -Variable PushOutput
The weird thing is, when git push succeeds, I get a sad error to my console. And the git push output doesn't make it to my screen.
Here's the deal: git push writes "Everything up to date" to STDERR.
PowerShell turns everything that came in on STDERR from a string into an ErrorRecord. So I get an ErrorRecord instead.

It's worse when the push does something; the output of the push report is obscured.
Why does git send these happy messages to STDERR?
Because it's reserving STDOUT for data that might be piped to another Linux command.
Git writes to STDERR for lack of Write-Host.

PowerShell distinguishes between data output and messages to the user. 💝
Piping through my process converts those ErrorRecords in the pipe back into strings, and everything works as expected.

git push 2>&1 | fix-stderr | Tee-Object -Variable PushOutput
You can follow @jessitron.
Tip: mention @twtextapp on a Twitter thread with the keyword “unroll” to get a link to it.

Latest Threads Unrolled: