1

I'm trying to use a foreach loop in Windows Powershell to download some images and I think I'm close, but I can't seem to get it right.

The command:

foreach ($name in @('avatar_1', 'avatar_2', 'avatar_3', 'avatar_4', 'avatar_5', 'avatar_6', 'avatar_7', 'thumbnail_1'))
>> {
>> wget https://raw.githubusercontent.com/flutter/codelabs/main/animated-responsive-layout/step_04/assets/$name.png -O $name.png
>> }

It seems to loop through the list, but it just results in the below for each item:

StatusCode        : 200
StatusDescription : OK
Content           : {137, 80, 78, 71...}
RawContent        : HTTP/1.1 200 OK
                    Connection: keep-alive
                    Content-Security-Policy: default-src 'none'; style-src 'unsafe-inline'; sandbox
                    Strict-Transport-Security: max-age=31536000
                    X-Content-Type-Options: nosniff
                    ...
Headers           : {[Connection, keep-alive], [Content-Security-Policy, default-src 'none'; style-src 'unsafe-inline'; sandbox], [Strict-Transport-Security, max-age=31536000],
                    [X-Content-Type-Options, nosniff]...}
RawContentLength  : 61683

I know the command works as wget https://raw.githubusercontent.com/flutter/codelabs/main/animated-responsive-layout/step_04/assets/avatar_1.png -O avatar_1.png will download the image. I'm not sure if I'm using the $name variable correctly in the foreach loop. I don't really use Powershell that often but ran into the command during a codelab and I was interested in getting it to work.

Natzely
  • 706
  • 2
  • 8
  • 27

3 Answers3

2

Here's the updated version of your script. This worked fine for me.

foreach ($name in @('avatar_1', 'avatar_2', 'avatar_3', 'avatar_4', 'avatar_5', 'avatar_6', 'avatar_7', 'thumbnail_1'))
{
    $url = "https://raw.githubusercontent.com/flutter/codelabs/main/animated-responsive-layout/step_04/assets/${name}.png"
    $outputFile = "${name}.png"
    Invoke-WebRequest $url -OutFile $outputFile
}
Aswin P.M
  • 639
  • 6
  • 6
  • Thank you! This worked for me as well and it seems what I was missing was the combination of putting the URL and out file in quotes and adding the curly brackets around the `$name` variable. Solved! – Natzely Aug 13 '23 at 06:41
  • @Natzely, glad to hear your issue is solved, but the `{...}` enclosure is _not_ needed in this case (though you may choose to use it for visual clarity), because in string interpolation (inside `"..."`) a `.` isn't considered a part of a variable name. By contrast, `.` in the _unquoted_ argument `$name.png` is the _member-access operator_, which is why the argument failed (the `$name` value has no property named `png`, resulting in `$null`). Therefore, the only thing needed to make your original code work is to replace `-O $name.png` with `-O "$name.png"` – mklement0 Aug 15 '23 at 21:39
  • @Natzely, I forgot to mention that `https://raw.githubusercontent.com/flutter/codelabs/main/animated-responsive-layout/step_04/assets/$name.png` is _implicitly treated as if it were enclosed in `"..."`_, whereas `$name.png` _in isolation_ is not. – mklement0 Aug 15 '23 at 22:28
  • @Aswin P.M: While your answer is an _effective_ solution, it (a) introduces _incidental variations_ to the code in the question (switch from `wget` to `Invoke-WebRequest`) _without explaining why_ and (b) _bypasses_ the problem with the code in the question _without explaining how_. In short: it solves the very specific problem in the question, but doesn't promote an understanding of what the _general_ problem with the approach in the OP is. As noted, the `{...}` enclosure too is _incidental_ to the solution. – mklement0 Aug 16 '23 at 02:19
0

Have you tried replacing line with this?

wget “https://raw.githubusercontent.com/flutter/codelabs/main/animated-responsive-layout/step_04/assets/“ + $name + “.png -O “ + $name + “.png”
toyota Supra
  • 3,181
  • 4
  • 15
  • 19
tarrdawg
  • 1
  • 1
  • 1
    After switching it this format, I now get a `Invoke-WebRequest : A positional parameter cannot be found that accepts argument '+'.` error – Natzely Aug 13 '23 at 06:27
  • Your syntax is broken, because passing an _expression_ (such as involving `+` operations) requires enclosure in `(...)` - see [this answer](https://stackoverflow.com/a/73832199/45375) for details. – mklement0 Aug 15 '23 at 14:22
0

tl;dr

  • Replace -O $name.png with -O "$name.png" to prevent the .png part from being interpreted as a property access (which evaluates to $null, given that the [string] instance stored in $name has no property named png).

  • To ensure that an argument you mean to be a string composed of a mix of variable references and verbatim parts is always passed as such, enclose it in "...".


You've run into a subtlety in PowerShell's parameter parsing, i.e. in how PowerShell parses parameter values (arguments) in argument (parsing) mode:

  • https://raw.githubusercontent.com/.../$name.png - due to not starting with $ - is implicitly treated as if were enclosed in "...", i.e. as a expandable (double-quoted) string.

    • In other words: the above is equivalent to "https://raw.githubusercontent.com/.../$name.png", causing the reference to variable $name to be expanded in isolation, with .png being considered a verbatim part of the string. (See this answer for a concise summary of PowerShell's string-interpolation rules).
  • By contrast, $name.png - due to starting with $ - is interpreted as a member-access expression, i.e. the attempt to access a .png property on the object stored in variable $name.

    • Given that $name contains a string and that strings do not have a .png property, the expression evaluated to $null, which with external programs results in no argument getting passed.

    • Passing "$name.png" yields the desired result via explicit string interpolation; that is, the value of variable $name is followed by verbatim string .png


mklement0
  • 382,024
  • 64
  • 607
  • 775