Faustin Gashakamba
Faustin Gashakamba

Reputation: 171

How can I move from Windows traditional command line to the modern PowerShell?

I was used to a few command line tricks in Windows that increased my productivity a lot.

Now I am told that I should move to PowerShell because it's more POWERful. It took me a while to get a little bit hang of it (objects, piping, etc.), and there are a lot of great tutorials on how to get a few things done. However, some (relatively) basic trick still puzzle me. For instance, what is the equivalent of the FOR structure in PowerShell?

For example,

FOR %i IN (*.jpg) DO Convert %i -resize 800x300 resized/%i

The above line takes all of photos in a folder and uses the ImageMagick's Convert tool to resize the images and restores the resized imaged in a sub-folder called RESIZED.

In PowerShell I tried the command:

Dir ./ | foreach {convert $_.name -resize 800x300 resized/$_name}

This can't work despite all of the googling around I did. What is missing?

Upvotes: 1

Views: 289

Answers (3)

Walter Mitty
Walter Mitty

Reputation: 18940

Revised based on comments given below

There are many applications with the name "Convert". If I do

Get-Command Convert

on my computer. It shows me an app that is part of the Windows system. If PowerShell is running the wrong app on you, it's never going to work.

The solution will be to point PowerShell at the convert tool inside the ImageMagick program folder. A Google search on "ImageMagick PowerShell" will lead you to lots of people who have faced the same problem as you.

Upvotes: 0

4c74356b41
4c74356b41

Reputation: 72171

Use:

Get-ChildItem | foreach {convert $_.name -resize 800x300 resized/$($_.name)}

Or, perhaps, you need to pass the full name (with path), also showing a shorter syntax (using aliases):

gci | % {convert $_.fullname -resize 800x300 resized/$($_.name)}

Also, you might want to supply the full path to the executable.

Upvotes: 0

mklement0
mklement0

Reputation: 437197

Note that / rather than \ is used as the path separator in this answer, which works on Windows too and makes the code compatible with the cross-platform PowerShell Core editions.

tl;dr:

$convertExe = './convert' # adjust path as necessary
Get-ChildItem -File -Filter *.jpg | ForEach-Object { 
  & $convertExe $_.Name -resize 800x300 resized/$($_.Name)
}

Read on for an explanation and background information.


The equivalent of:

FOR %i IN (*.jpg)

is:

Get-ChildItem -File -Filter *.jpg

or, with PowerShell's own wildcard expressions (slower, but more powerful):

Get-ChildItem -File -Path *.jpg    # specifying parameter name -Path is optional

If you're not worried about restricting matches to files (as opposed to directories), Get-Item *.jpg will do too.

While dir works as a built-in alias for Get-ChildItem, I recommend getting used to PowerShell's own aliases, which follow a consistent naming convention; e.g., PowerShell's own alias for Get-ChildItem is gci

Also, in scripts it is better to always use the full command names - both for readability and robustness.

As you've discovered, to process the matching files in a loop you must pipe (|) the Get-ChildItem command's output to the ForEach-Object cmdlet, to which you pass a script block ({ ... }) that is executed for each input object, and in which $_ refers to the input object at hand.

(foreach is a built-in alias for ForEach-Object, but note that there's also a foreach statement, which works differently, and it's important not to confuse the two.)

There are 2 pitfalls for someone coming from the world of cmd.exe (batch files):

  • In PowerShell, referring to an executable by filename only (e.g., convert) does not execute an executable by that name located in the current directory, for security reasons.

    • Only executables in the PATH can be executed by filename only, and unless you've specifically placed ImageMagick's convert.exe in a directory that comes before the SYSTEM32 directory in the PATH, the standard Windows convert.exe utility (whose purpose is to convert FAT disk volumes to NTFS) will be invoked.
      Use Get-Command convert to see what will actually execute when you submit convert; $env:PATH shows the current value of the PATH environment variable (equivalent of echo %PATH%).

    • If your custom convert.exe is indeed in the current directory, invoke it as ./convert - i.e., you must explicitly reference its location.

    • Otherwise (your convert.exe is either not in the PATH at all or is shadowed by a different utility) specify the path to the executable as needed, but note that if you reference that path in a variable or use a string that is single- or double-quoted (which is necessary if the path contains spaces, for instance), you must invoke with &, the call operator; e.g.,
      & $convertExe ... or & "$HOME/ImageMagic 2/convert" ...

  • PowerShell sends objects through the pipeline, not strings (this innovation is at the heart of PowerShell's power). When you reference and object's property or an element by index as part of a larger string, you must enclose the expression in $(...), the subexpression operator:

    • resized/$($_.Name) - Correct: property reference enclosed in $(...)
    • resized/$_.Name - !! INCORRECT - $_ is stringified on its own, followed by literal .Name
      • However, note that a stand-alone property/index reference or even method call does not need $(...); e.g., $_.Name by itself, as used in the command in the question, does work, and retains its original type (is not stringified).
    • Note that a variable without property / index access - such as $_ by itself - does not need $(...), but in the case at hand $_ would expand to the full path. For the most part, unquoted tokens that include variable references are treated like implicitly double-quoted strings, whose interpolation rules are summarized in this answer of mine; however, many additional factors come into play, which are summarized here; edge cases are highlighted in this question.
    • At the end of the day, the safest choice is to double-quote strings that contain variable references or subexpressions:
      • "resized/$($_.Name)" - SAFEST

Upvotes: 2

Related Questions