PowerShell Invoke-WebRequest: The Other cURL
When you first encounter web APIs or even just want to grab a webpage from the command line, the tool you’ll see in nearly every tutorial is cURL. It’s been around since 1997, created by Swedish developer Daniel Stenberg. Originally it was a small project called “httpget” that grew into “curl” (short for Client URL). Stenberg wanted a tool that could fetch currency exchange rates for IRC users, but it quickly expanded into a universal command-line HTTP client. Today, cURL is bundled with nearly every Linux distribution and even ships with Windows and macOS.
In PowerShell, we have our own native tool: Invoke-WebRequest. At first glance, it looks like a clone, but it’s not. Where cURL spits out raw text, Invoke-WebRequest returns structured objects. That difference - objects instead of strings - changes the way you think about automation. It’s the difference between parsing a wall of text with regex and simply navigating properties with dot notation.
Invoke-WebRequest has its own history. It didn’t even exist in PowerShell v2. When it arrived in v3, it was tied to Internet Explorer’s engine and required the -UseBasicParsing switch to avoid dependency issues. That quirk haunted many early adopters. By v5.1 the cmdlet had stabilized, and in PowerShell 7 it became cross-platform; finally shedding its Windows-only roots. Understanding this evolution helps you appreciate why some old blog posts look so different from what you see today.
The Evolution of Invoke-WebRequest
In v2, you had nothing - people resorted to .NET’s System.Net.WebClient or HttpWebRequest classes. In v3, Invoke-WebRequest appeared, but it was clunky. The -UseBasicParsing switch was infamous, and the cmdlet often returned inconsistent results. By v5.1, it was reliable enough to be used in production scripts. And in v7, it became a true cross-platform tool, working on Linux and macOS as well as Windows.
Let’s see what a command to download a webpage looks like in Powershell v2 compared to Invoke-WebRequest in Powershell v3+.
In Powershell v2 we need to use the .NET WebClient class:
# PowerShell v2 (using .NET classes)
# Initialize a WebClient object
$wc = New-Object System.Net.WebClient
# Create a URI variable
$url = "https://dog.ceo/api/breeds/image/random"
# Create a filename variable
$outfile = "C:\Temp\Dog.png"
# use the downloadstring method to download the json
$json = $wc.DownloadString($url)
# create a regex to extract the image URL from the JSON response
$regex = '"message"\s*:\s*"([^"]+)"'
# Extract the image URL from the JSON response
if ($json -match $regex) {
$imageUrl = $matches[1]
}
# Remove escape characters from path
$imageUrl = $imageUrl -replace '\\', ''
# Download the image file
$wc.DownloadFile($imageUrl, $outfile)
# Open the image file
Invoke-Item $outfileNow perform the same operation using Invoke-WebRequest:
# Create a URI variable
$url = "https://dog.ceo/api/breeds/image/random"
# Create a filename variable
$outfile = "C:\Temp\Dog.png"
# Invoke-WebRequest to download the json
$json = Invoke-WebRequest -Uri $url
# Extract the image URL from the JSON response
$imageUrl = $($json.Content | ConvertFrom-Json).message
# Download the image file
Invoke-WebRequest -Uri $imageUrl -OutFile $outfile
# Open the image file
Invoke-Item $outfileInvoke-WebRequest vs cURL
cURL is terse and flag-driven. It was designed for speed and minimalism. Invoke-WebRequest is verbose and parameter-driven. It was designed for readability and discoverability.
Which style feels more natural to you? Do you prefer the brevity of cURL or the clarity of PowerShell?
Hands-On: Downloading a Webpage
Let’s do something simple but practical: download a webpage and save it to disk.
$url = "https://example.com"
$outfile = "example.html"
Invoke-WebRequest -Uri $url -OutFile $outfile
Get-Item $outfileThis is the PowerShell equivalent of curl -o example.html https://example.com. The difference is that PowerShell makes the intent explicit.
Additional examples:
Download a binary file (like an image):
Invoke-WebRequest -Uri "https://picsum.photos/200/300" -OutFile "random.jpg"Download multiple files in a loop:
$urls = @(
"https://picsum.photos/200/300",
"https://picsum.photos/300/200",
"https://picsum.photos/400/400:
)
foreach ($url in $urls) {
$filename = (Split-Path $url -Leaf) + ".png"
Invoke-WebRequest -Uri $url -OutFile $filename
}Here’s where Invoke-WebRequest shines: it doesn’t just give you the HTML. It parses the page and gives you objects. You can explore $response.Links or $response.Images directly. That’s something cURL doesn’t do - you’d have to parse the HTML yourself.
Exploring Parameters and Internals
PowerShell encourages you to look behind the curtain. You can inspect Invoke-WebRequest to see what it can do and how it’s built.
Syntax and Parameter Discovery
Start with the basics:
Get-Command Invoke-WebRequest -SyntaxThis gives you every overload of the cmdlet, showing which parameters are available and how they’re grouped. It’s the fastest way to see what’s possible.
Then go deeper:
Get-Help Invoke-WebRequest -FullThis reveals parameter descriptions, default values, and usage notes. Scroll through and highlight anything you find unfamiliar - like -MaximumRedirection, -WebSession, or -SkipHeaderValidation.
Want a quick list of all parameter names?
(Get-Command Invoke-WebRequest).Parameters.KeysThis is a great way to compare across versions or check for hidden parameters.
Inspecting the Cmdlet Internals
In compiled cmdlets, you won’t see the full script block, but you can still inspect metadata:
Get-Command Invoke-WebRequest | Format-List *This shows the module, version, visibility and more. If you’re using a script-based function (like in a custom module), you can even extract the source code:
(Get-Command Add-VPNConnection).ScriptBlockIn most cases, this will be $null for built-in cmdlet, but it’s a good habit to check.
Exploring the Output Object
Invoke-WebRequest doesn’t just return HTML - it returns a PowerShell object:
$response = Invoke-WebRequest https://example.com
$response | Get-MemberThis reveals properties like:
.Content- the raw HTML or response body.StatusCode- the HTTP status code.Headers- a dictionary of response headers.Links- parsed anchor tags.Images- parsed image tags.RawContentLength- size of the response.BaseResponse- underlying .NET response object
Example usage:
$response.Headers["Content-Type"]
$response.Links | Select-Object href
$response.Images | Select-Object srcThis is where PowerShell’s object model shines. You’re not using regex to search strings - you’re navigating a structured response.
Module and Version Awareness
Get-Module Microsoft.PowerShell.UtilityThis tells you which version of the module provides Invoke-WebRequest. If you’re troubleshooting across environments, this is essential. In Windows Powershell the Invoke-Webrequest command should show at least 3.0. In current versions of Powershell Core, this will show at least 7.0.
Bonus:
(Get-Command Invoke-WebRequest).ModuleName
(Get-Command Invoke-WebRequest).Version
These help you track changes across PowerShell versions and environments.
Related Cmdlets and Aliases
In addition to Invoke-WebRequest, there are related cmdlets that can be useful:
Invoke-RestMethod- for JSON APIsNew-WebServiceProxy- for SOAP servicesTest-Connection- for pinging endpoints
In Powershell, curl is an alias for Invoke-WebRequest. In order to use the native curl.exe, you need to call it explicitly using the .exe suffix.
# Show the alias for curl
Get-Alias curl
# Using the alias (Invoke-WebRequest)
curl https://example.com
# Using the native curl.exe
curl.exe https://example.com
Extended Practice
Now let’s stretch a bit. Try adding headers to a request:
$headers = @{ “User-Agent” = “ScriptShackDemo” }
Invoke-WebRequest -Uri “https://httpbin.org/headers” -Headers $headers
Try a POST request:
$body = @{ name = “Shacker”; session = 4 }
Invoke-WebRequest -Uri “https://postman-echo.com/post” -Method POST -Body $body
Try downloading JSON and parsing it:
$json = Invoke-WebRequest -Uri “https://jsonplaceholder.typicode.com/todos/1”
$obj = $json.Content | ConvertFrom-Json
$obj.title
Try handling redirects:
Invoke-WebRequest -Uri “https://tinyurl.com/class09262025” -MaximumRedirection 3
Notice how the response is structured. You can drill into $response.Content, $response.StatusCode, or $response.Headers. This is where Invoke-WebRequest feels less like a command-line tool and more like a programming interface.
Wrap-Up and Homework
Invoke-WebRequest is PowerShell’s answer to cURL, but it’s not just a clone. It’s an object-oriented cmdlet that has grown from a clunky Windows-only tool into a cross-platform workhorse. Knowing its history helps you understand why some scripts look different, and knowing its parameters helps you write scripts that are clear and maintainable.
Homework
Use
Get-Command Invoke-WebRequest -Syntaxto list every parameter available in your version of PowerShell. Compare your results to the version comparison table.Run
Get-Module Microsoft.PowerShell.Utilityand note the version number.Write a script that uses
Invoke-WebRequestto download three images from a free API (for example,https://picsum.photos/200/300) and save them to disk.Write a script that uses
curl.exeto do the same.Which approach do you prefer and why?
Next week, we’ll build on this by exploring the newer, REST focused Invoke-RestMethod command. We’ll look at how it’s different from Invoke-WebRequest and when to use each.

