DEV Community

Cover image for PowerShell Tutorial (Especially for People Who Hate PowerShell)
Ryan Palo
Ryan Palo

Posted on • Originally published at

PowerShell Tutorial (Especially for People Who Hate PowerShell)


This is a long article. I'm putting a table of contents up here for the impatient, so you can find the specific section you're looking for.

  1. Getting Started with PowerShell
  2. Some Useful Commands to Get Started
  3. Just Tell Me How to Do the Thing!
  4. Learning to Love PowerShell

My relationship with the terminal -- especially Windows PowerShell -- has been a bit of a roller coaster.

I first discovered the terminal when I heard about Python and didn't know anything about anything. Like the average person who uses computers for games and applications like Excel and PowerPoint, I went to the Python Site, downloaded the installer, installed it, and clicked the icon. Imagine my horror when something like this popped up:

The Python REPL

Where are the buttons? What's with the old-timey font? What do I do with my mouse?

After beginning to program more and more and learning about the terminal, I began to love Bash. Is it archaic? Yes. Are the commands nonintuitive and difficult to remember? Heck, yes. For some reason, this just made me love it more.

I had to work hard at it, and typing a bunch of what appears to be gobbledygook that had powerful results made me feel like a wizard. For instance, typing du -sk * | sort -n | tail (See? Gobbledygook!) is a really fast way to see the top largest directories in your current directory.

As I learned more, I was able to customize how it looked and get fonts, colors, and promts that made the terminal that much more inviting.

So I decided to take my newfound programming powers to work. Except that I work as a mechanical engineer, and that typically means SolidWorks and Windows. So I opened up the recommended terminal, PowerShell. And again, to my horror:

The PowerShell terminal

No problem, I thought. I'll just go to the preferences screen.

PowerShell preferences

Oh, no. We're not in Kansas anymore. Not exactly what I was used to. So I went into PowerShell and tried to do some simple things. Some things worked fine! ls, cd, and mkdir all worked like I was used to. But I couldn't figure out why setting the $PATH was so hard. Adn what was with all of these backslashes? How come I couldn't sudo?

More and more little irritations that kept reminding me I wasn't using Bash kept cropping up. I gave up and installed Cygwin, which allowed me to have a Bash experience on my Windows 7 computer. Except not quote. Everything I tried to do to get Bash on my Windows machine was just ... not quite right.

Finally, I tried another approach and began working on learning PowerShell the right way, from the beginning, as another language. As I learned, I found myself buying into the whole PowerShell philosophy and noticing things that were a little nicer than when I was using Bash. Keep in mind, like everything, PowerShell is just another tool in the toolbox, and whether or not it's the best tool really depends on the situation.

I'm not going to give you a complete lesson on the basics of the PowerShell language. I'm also not going to spend a lot of time telling you how to install it. This set of docs is fairly comprehensive, and it's also a good cheat sheet for reference later.

My goal here is to show you how things translate from other shells and get you started on your own PowerShell journey, so I'll assume that you have at least a little bit of experience with a shell language like Bash.

Getting Started with PowerShell

There are mental shifts away from how Bash-like shells do things that you need to make when you're starting to learn PowerShell. If you can get past your initial gut reaction of disgust when you see these differences, you might start to see that they actually make you more productive.

Everything is an Object

This is probably the biggest difference that you have to get through your head. In Unix shells, everything is plain text. This makes things nice because you can expect text input into all your scripts and you know that if you output text, everything will probably be OK.

However, the downside is that this makes inspecting specific data a nightmare of text parsing, and it makes working with anything other than text (floating point numbers, anyone?) a real pain.

In PowerShell, which is actually built on top of .NET, everything is an object. This will feel very comforting if you're coming from Python, Ruby, JavaScript, or a similar language background. Let's see some examples and get our hands dirty. Open up your PowerShell interpreter.

Side note: I put a PowerShell prompt (PS>) in front of each command so you can tell what is output. Every line that starts with PS> is something for you to type. Everything else is output.

PS> Get-Process
Enter fullscreen mode Exit fullscreen mode

You should see a pretty long string of text. We don't have to stand for that! We are terminal folk. Code flows through our veins! Try this:

PS> Get-Process | Sort-Object CPU -descending | Select-Object -first 10
Enter fullscreen mode Exit fullscreen mode

Now you should see a shorter list, reverse sorted by CPU time. If you're already getting itchy because all of these commands and options are so long, I address that in a couple sections. Stay with me.

The important thing to pay attention to here is the headers at the top of each column. Each row of this table is actually a System.Diagnostics.Process object, not just a row of a table. Don't believe me? Just check!

PS> (Get-Process)[0] | Get-TypeData
Enter fullscreen mode Exit fullscreen mode

See there? The Get-Process command returned a list of Process objects, and we were able to select the first one through indexing (without splitting the output by \n!) and shuffle that through the Get-TypeData command. These items being objects gives us some power. What if we only wanted their ProcessName?

PS> Get-Process | Sort-Object CPU -descending | Select Object ProcessName -first 10
Enter fullscreen mode Exit fullscreen mode

See how easy it was to access that? We didn't have to cut the fields delimited by tabs or colons and count which field we wanted (1...2...3...4...5!). We just told it that we wanted the ProcessName attribute. No more parsing, splitting, joining, formatting output, etc.

Objects Have Types

Another side effect of being on top of .NET and having everything be an object is that everything must have a type. This may be confusing coming from a Bash shell that only has strings and strings that might get interpreted as commands. Here's what it does for us.

PS> 2 + 2
Enter fullscreen mode Exit fullscreen mode

Ah! Are you amazed? That would take way more work in Bash! And don't even get me started on:

PS> 4.2 / 3
Enter fullscreen mode Exit fullscreen mode

PowerShell usually does a pretty good job of trying to figure out the types that you mean so you can feel as dynamic as with other shells and scripting languages and not have to strictly have types on everything. But, if you really want to enforce a type, you can do so by prepending the object with its type in square brackets.

PS> "2" + 2
22  # A string
PS> [Int]"2" + 2
4  # An integer.  The conversion only applies to the "2"
Enter fullscreen mode Exit fullscreen mode

Predictable Command Structure

This was one of the first things I noticed when I started using PowerShell. I was looking at example commands on StackOverflow and I kept getting mad at how long the PowerShell commands were in comparison to the Bash commands. For example, to list the contents of your current directory in Bash:

$ ls -l
Enter fullscreen mode Exit fullscreen mode

In Powershell:

PS> Get-ChildItem
Enter fullscreen mode Exit fullscreen mode

It's so long! And capital letters? Come on. The good news is that you don't have to type the whole thing out if you don't want to. But, before I get to that, let me explain the rationale behind it.

Why So Long?

The creators of PowerShell wanted the commands to be extremely intuitive, such that you could almost guess the command that you needed. This would be basically impossible with Bash. There's no way that you could guess that cat is the common command to read a file to the terminal. Once you learn it, it makes sense that it's short for "concatenate," but it's not intuitive by itself.

PowerShell commands were designed after a simple pattern: "Verb-Noun." The creators of PowerShell tried to keep the number of verbs to a minimum. Common ones you'll see are Get, New, Add, Clear, Export, Remove, Set, Update, and Write. The nouns are also usually pretty intuitive: Process, Item, Object, Date, Job, and Command are a few.

This consistent pattern allows someone to look at a script that has commands in it that they've never used and still have some idea about what the script is doing.

The other benefit of keeping a consistent pattern is that PowerShell can detect the "verb" and "noun" sections of commands. For example, do you want to see all of the commands with the verb "New?"

PS> Get-Command -verb New
Enter fullscreen mode Exit fullscreen mode

In fact, if you know what you want to do, but you can't remember the command for it, Get-Command is one of a few commands that will be your friend. What if you wanted to see what kinds of things you could do to "Job" objects?

PS> Get-Command -noun Job
Enter fullscreen mode Exit fullscreen mode

Yes, the commands are longer to type than their equivalent, typically terse Bash commands, but with this additional verbosity comes ease of use, less memorizing of commands, and helpful tooling to keep you productive.

Aliases Make Your Life Easy

Even though the commands are long and verbose, PowerShell knows that people using the terminal are lazy (in a good way!), and it doesn't want to get in your way. It has a ton of built-in aliases to make your life easier and make you feel more comfortable, and it can actually do some aliasing on the fly.

Remember that command we ran earlier to see all of the files in our directory?

PS> Get-ChildItem

# You can also do:
PS> gci
PS> dir

# And just to make you feel a little bit more at home...
PS> ls
Enter fullscreen mode Exit fullscreen mode

Want to see all of the available aliases? By this point, you shouldn't be surprised that the command is:

PS> Get-Alias
Enter fullscreen mode Exit fullscreen mode

Using these aliases during shell sessions can keep you productive and save your fingers from wearing out without giving up the functionality and readability of the longer commands they stand for.

Best Practice for Scripting

Just a word on best practice: when you're doing command-line work by yourself, feel free to use as many aliases as you want. Alias it up. Get your productivity on.

However, if you're writing a script or sharing code with someone, it's probably best to type the whole command and whole flag names out. Trust me, your future self and coworkers will thank you.

Some Useful Commands to Get Started

While the best way to learn PowerShell is to dive in and practice, I'm going to share some commands with you here that are really helpful when you're a little stuck.

When You're Not Sure Which Command to Use

PS> Get-Command
Enter fullscreen mode Exit fullscreen mode

This command will provide you with more information about available commands. You can zero in on what you want by specifying -verb or -noun parameters. In order to get more information about one or two particular commands, pipe the output into Format-List. This will give you the options, location, and some other useful features.

PS> Get-Command Get-Alias | Format-List
Enter fullscreen mode Exit fullscreen mode

When You're Not Sure What a Command Does

PS> Get-Help command

# You can also get help by adding the ? parameter
PS> command -?
Enter fullscreen mode Exit fullscreen mode

Get-Help is roughly the man of the PowerShell world. Are you starting to see the benefits of intuitive commands yet? Actually, Get-Help has quite a few useful flags as well. It's probably a good idea to take a look at them using the method we discussed above:

PS> Get-Command Get-Help | Format-List

# Or, if you're feeling cheeky:

PS> Get-Help Get-Help
Enter fullscreen mode Exit fullscreen mode

My favorite is that you can ask it specifically for example usage only.

PS> Get-Help Get-Alias -examples
Enter fullscreen mode Exit fullscreen mode

When You're Not Sure What Properties Your Object Has

PS> Get-Process | Get-Member

# Another similar command:

PS> (Get-Process)[0] | Format-List
Enter fullscreen mode Exit fullscreen mode

If you know what data you want, but you just don't know what it's called, or if you aren't even sure what data is available to you, these commands will help you "see" your objects a little better.

When You Want to Get a Portion of the Data

PS> Get-Process | Select-Object Id, ProcessName -last 20
Enter fullscreen mode Exit fullscreen mode

Select-Object is your general purpose stuff-whittler. You can specify particular attributes you want and how many you want.

When You Want to Filter Your Data

PS> Get-Process | Where-Object WS -gt 150MB
Enter fullscreen mode Exit fullscreen mode

There are a few ways to use the Where-Object command, but this is the simplest one. In the example above, I selected only the processes whose working set (memory usage) was greater than 150MB. (Also, can we gush a little about how PowerShell can do KB/MB/GB math?)

Just Tell Me How to Do the Thing!

This last section will be just a few snippets for the impatient among you. If you're just trying to get one darn thing done in PowerShell and you can't make it work, these tips should hopefully help.

Basic Unix Commands Translated

# pwd
PS> Get-Location  # or gl or pwd

# ls
PS> Get-ChildItem  # or gci or dir or ls

# cd
PS> Set-Location  # or sl or chdir or cd

# cp
PS> Copy-Item  # or copy or cpi or cp

# mv
PS> Move-Item  # or move or mi or mv

# cat
PS> Get-Content  # or gc or type

# mkdir
PS> New-Item -ItemType Directory  # or ni -ItemType Directory or mkdir

# touch
PS> New-Item -ItemType File  # or ni

# rm
PS> Remove-Item  # or del or erase or ri or rm

# rm -rf
PS> Remove-Item -Recurse -Force  # or rm -recurse -force

# head or tail
PS> Select-Object -first # or -last
# usage: Get-Process | Select-Object -first 10

# find
PS> Get-ChildItem -filter *.rb -recurse .
# but, for a slightly easier to read version:
PS> Get-ChildItem -filter *.rb -recurse . | Select-Object FullName
Enter fullscreen mode Exit fullscreen mode

Access the Path (and Other Environment Variables)

In PowerShell, a lot of things get treated like file locations -- and environment variables are no exception. These special groups of file-like variables are called PSDrives. In the same way you can ask the C: drive what file is at "\Users\ryan\desktop" with a Get-ChildItem C:\Users\ryan\Desktop, you can do the same thing with env:, the environment PSDrive.

PS> Get-ChildItem env:

# and to get a specific one
PS> Get-Content env:PATH
Enter fullscreen mode Exit fullscreen mode

One super neat thing about these PSDrives is that you can actually read a file location like it's a variable. So you can also get to an environment variable this way:

PS> $env:PATH
Enter fullscreen mode Exit fullscreen mode

This second way is probably the most popular way to get the PATH variable.

Customize Your Profile

If you're into command lines and you're anything like me, you're probably wondering how to customize them. The answer lies in $profile.

Finding the Profile

There are actually several profiles, depending on which "Host" you're using to interface with PowerShell. For instance, if you're just using the regular PowerShell command line, the name of your profile will be Microsoft.PowerShell_profile.ps1. However, if you are working in the PowerShell Integrated Scripting Environment (ISE), your profile will be Microsoft.PowerShellISE_profile.ps1. You can mostly ignore this, because if you ever want to know, just ask:

PS> $profile
Enter fullscreen mode Exit fullscreen mode

However, there are additional options. If you want to create a profile that will work for the ISE or the regular command line, you'll want $profile.CurrentUserAllHosts. Or, if you want to configure a profile for all users on your computer, you'll want $profile.AllUsersCurrentHost. There are a few options, and you can see them all with:

PS> $profile | Get-Member -type NoteProperty
Enter fullscreen mode Exit fullscreen mode

Creating Customizations

Your profile works just like .bash_profile does in Bash. It's just a script that gets run before you start working in PowerShell. You can add aliases (although see the note below), functions, variables, and set custom settings. The simplest way to check if you already have a profile is:

PS> Test-Path $profile
Enter fullscreen mode Exit fullscreen mode

And to start creating your profile:

# Use whichever editor you love best
PS> code $profile
Enter fullscreen mode Exit fullscreen mode

Here are a couple of useful settings you might like:

# Microsoft.PowerShell_profile.ps1

# You can customize the window itself by accessing $Host.UI.RawUI
$window = $Host.UI.RawUI
$window.WindowTitle = "Pa-pa-pa-pa-pa-pa-POWERSHELL"
$window.BackgroundColor = "Black"
$window.ForegroundColor = "Gray"

# You can define functions (remember to follow the Verb-Noun convention!)
function Count-History {
  (Get-History | Measure-Object).count

function beep {
  echo `a

function Edit-Profile {

  vim $profile

# You can set aliases.
# NOTE: In PowerShell, you can only alias simple commands.
# Unlike Bash, you can't alias commands with arguments flags.
# If you want to do that, you should define a function instead.
Set-Alias touch New-Item  # old habits die hard, amirite?

# You can customize your prompt!
function prompt {
  # ... see the next section for details
Enter fullscreen mode Exit fullscreen mode

Customize Your Prompt

There are two ways to do this: the simple way and the complicated way.

The Simple Prompt

The simplest way to customize your prompt is by defining the prompt function, either manually or in your profile. For example:

function prompt {
  $histCount = (Get-History | Measure-Object).count
Enter fullscreen mode Exit fullscreen mode

This is an amusing way to print the number of inputs that you've already typed inside your prompt. Whatever string you return is what gets set as the prompt.

The Complicated Prompt

Basically, you can do whatever you want in your prompt function, as long as it returns a string at the end. For example, here's a more complicated prompt.

function prompt {
  $loc = (Get-Location).Path.Replace("$HOME", "~")
  $gitBranch = git branch | Select-String "\*"
  if (!$gitBranch) {
    $gitBranch = ""
  } else {
    $gitBranch = $gitBranch.ToString().Replace("`* ", "")
  $hisCount = (Get-History | Measure-Object).count
  WriteHost -ForegroundColor yellow "`n $loc"
  WriteHost -NoNewLine "PS [$histCount] $gitBranch ->"
  return " "
Enter fullscreen mode Exit fullscreen mode

You can see that I achieve a multi-line prompt by using Write-Host a number of times. Lastly, I simply return a space. My prompt ends up coming out like this:

PS [102] master ->
Enter fullscreen mode Exit fullscreen mode

Get It to Not Look Ugly

This is a tough one. Like I mentioned above, on Windows, PowerShell runs in a window with not very many customization options. Especially for someone who is used to being able to tweedle every knob of their text editor to their liking, it's almost painful. There are two pretty good alternatives, though.

  1. Cmder: This one is built on top of ConEmu, which is a pretty popular terminal emulator for Windows. If you like Notepad++, I think you'll really like this, because it feels similar.

  2. Hyper: This one is nice for those of you that don't have a vendetta against Electron applications and are more front-end-edly minded. The customization and settings are all done in JavaScript and CSS, which makes searching for help really nice. As might be expected from the JavaScript crowd, there are about a gazillion plugins, some of which are stable and quite good. There's a lot of work being done on Hyper right now, so you may have some stability issues, but over the last few months, it's really gotten quite a bit faster and hiccup-less.

Find More Resources

The best place to go is definitely the official docs. There's so much content there that they should have the answers you need. Since PowerShell is now open-source, you can also check out their GitHub repositories. There are some great docs and getting started guides there as well.

For those who like to learn from books, I would recommend the Windows PowerShell Cookbook by Lee Holmes, as well as Windows PowerShell in Action by Bruce Payette and Richard Siddaway. There is a lot of knowledge in those two books.

Learning to Love PowerShell

The goal of this article is to show that just because PowerShell is different from what you are used to, it's not necessarily bad.

If you take the time to really dig into the differences and learn the reasoning and design decisions behind them, you'll start to see their strengths and value. You might actually miss dealing with typed objects in your command line when you go back to other shells.

Having to parse text streams might start to feel archaic. You might find yourself using longer, more consistent function names everywhere as you realize how nice it is to be able to read and understand your scripts six months after you write them.

Or, you might simply stop telling people how much you hate PowerShell whenever they mention it.

Originally posted on Simple Programmer

Discussion (17)

zeerorg profile image
Rishabh Gupta

I haven't learned PowerShell but I am pretty positive about what it wants to do, that is make scripting easier and more programming like. My greatest pain when working with linux is writing a bash script.
PowerShell is different from other shells on linux as it focuses more on verbosity and being script friendly than just being a shortcut to do stuff.

zeerorg profile image
Rishabh Gupta

Also a great post !!

rpalo profile image
Ryan Palo Author

Thanks! Glad you liked it!

shalvah profile image

Or, you might simply stop telling people how much you hate PowerShell whenever they mention it.

I think I'll stick with this. I've gotten used to PowerShell, customized it to be somewhat like Zsh, but some things still annoy me. Like the fact that history is only saved for the current session by default. And the fact that right-clicking does a paste. And the fact that the shell handles windowing and line wrapping SO POORLY. And the fact that I have to confirm another prompt anytime I hit Ctrl-C. Oh, and the fact that the shell is SO DAMN UGLY no matter how much you customise it.

I get the whole C#-like thing, and I could even come to like it, but I don't understand the design decisions that led to the things above; for a team like Microsoft, it's really disappointing.

rpalo profile image
Ryan Palo Author

Hey, that’s fair! And if you got to the last line, it means you read the whole article, so that’s good enough for me 😁 do check out hypertext and/or cmder though. They make things a little nicer. Thanks for reading!

shalvah profile image
Shalvah • Edited

Lol. Bad News: I skipped to the end. Good News: so I could find the "Bookmark" button

Thread Thread
shalvah profile image

So, two and a half years later, I finally finished this article!😄

I really love it. Well-written, very comprehensive and lots of useful tips. Plus a lot of the reactions you described were things I'd experienced too.

I'm in a different place now... In late 2018, I decided to learn PS, and I actually began to appreciate it. The host is still terrible, so I use Cmder (might consider the Windows Terminal when it's out).

Thanks for writing this.

aglamadrid19 profile image
Alvaro Lamadrid

I find PowerShell very intuitive so far. The Object Oriented and Verb-Noun design has impacted positively my effectiveness at the time of Scripting (Windows Platform). And now with PowerShell Core, things will go to the next level I'm sure. Are you planning on writing about that (PS Core)? Thank you for the article Ryan.

rpalo profile image
Ryan Palo Author

I hadn’t planned on it, but I’ll look into it, thanks!

kennethrow profile image
Ken Row

Beware that Powershell's new-item will clobber a pre-existing file, whereas Bash's touch will just update its modification time. If you want a PowerShell touch to really behave like Bash, it'll require a function, not an alias.

ltvan profile image
Van Ly

I think that you should include this in your post for completeness:

ertant profile image
Ertan Tike

Post is great. It's help me to understand that i miss a lot.


I still hate power shell. Why? Because power shell is one of 2005-era MS fallacy. (Like Win-FS, Metro UI among other etc) They take something does not work very well and re-design from scratch to sail to new seas.

Idea behind shell is you have some of "executables" to do some things. You call these executables from shell or you can put in a script file or you can call they from other executables. Some of executables are preinstalled (like ls, cp, mv) and if you don't have that executables you can install them (like git, npm, etc).

Power shell "cmdlets" are not executables, they are some registered commands which you do don't know where they come from.

Lets say you want to set some firewall settings from shell. Try to find related "cmdlet" if you can.

Lets say you found the "cmdlet" and if is not registered or if it's not compatible with your OS good luck with that.

Lets say you can run "cmdlet" and if you do not know object model of firewall library, go read some msdn pages and return back.

This is a part of "cmdlets" of firewall;


now compare this with how git or docker commands work. ( Wait... "Disable-NetFirewallRule" and "Enable-NetFirewallRule" ? why my friend??.. why?... )

I understand power shell is written with good intensions and super idealistic ideas to put some standard but I think power shell is not written to replace daily shell work, it's written to execute some (written in tears) script files and automate something.

Worst thing is; if I have to automate something, I could prefer to write in C# with IDE support (auto complete, debugging, refactoring etc etc) not in power shell. Because they are sailing in same sea.

Maybe I get older and wrong, I don't know.

itsjzt profile image
Saurabh Sharma • Edited
kazamario profile image


negibirendar profile image

if i am working with remote deployment and i need to pass local variable to remote machine, how i can

rpalo profile image
Ryan Palo Author

I found this article after a quick search. For PowerShell 3, but seems like it might work. What do you think?