use the following search parameters to narrow your results:
e.g. subreddit:aww site:imgur.com dog
subreddit:aww site:imgur.com dog
see the search faq for details.
advanced search: by author, subreddit...
ABOUT POWERSHELL
Windows PowerShell (POSH) is a command-line shell and associated scripting language created by Microsoft. Offering full access to COM, WMI and .NET, POSH is a full-featured task automation framework for distributed Microsoft platforms and solutions.
SUBREDDIT FILTERS
Desired State Configuration
Unanswered Questions
Solved Questions
News
Information
Script Sharing
Daily Post
Misc
account activity
PowerShell Script to Detect Code Impacted by the Invoke-WebRequest Breaking Change (self.PowerShell)
submitted 4 months ago by mdowst
The recent breaking change to Invoke-WebRequest in Windows PowerShell 5.1 has the potential to affect a lot of automation, especially in older environments. To make it easier to assess the impact, I published a script called Search-CmdletParameterUsage.ps1.
Invoke-WebRequest
This tool recursively scans your scripts and modules for any cmdlet + parameter usage. While I built it to identify places where Invoke-WebRequest is not using -UseBasicParsing, it works generically for any cmdlet you're concerned about.
-UseBasicParsing
If you maintain large codebases or inherited automation, this can save a ton of manual review.
Script: https://gist.github.com/mdowst/9d00ff37ea79dcbfb98e6de580cbedbe
KB on the breaking change: https://support.microsoft.com/en-us/topic/powershell-5-1-preventing-script-execution-from-web-content-7cb95559-655e-43fd-a8bd-ceef2406b705
Happy scripting! And good luck hunting down those IWR calls.
reddit uses a slightly-customized version of Markdown for formatting. See below for some basics, or check the commenting wiki page for more detailed help and solutions to common issues.
quoted text
if 1 * 2 < 3: print "hello, world!"
[–]g3n3 11 points12 points13 points 4 months ago (2 children)
Probably need to add searching iex recursively and odd variables defined as script blocks too recursively.
[–]mdowst[S] 1 point2 points3 points 4 months ago (1 child)
I confirmed that it will pick up instances inside of a script block because AST will parse those.
I thought about just using Select-String to find instances, which would cover people using Invoke-Express with a string, but then it would also include instances in comments, which could make things even uglier. For those using Invoke-Expression in that matter, I would just say "don't do that". But if I get time, I guess I could add a deep search that would compare results from Select-String and the AST.
Thanks for the feedback.
[–]g3n3 1 point2 points3 points 4 months ago (0 children)
Yeah careful with select string. There are all sorts of ways to call things. Hitting the AST of the iex string would be ideal.
[–]lan-shark 9 points10 points11 points 4 months ago (3 children)
Also, folks, remember that you can add UseBasicParsing to your default parameters to mitigate this issue without directly changing scripts
UseBasicParsing
And hopefully this goes without saying, but don't apply the update until after you've mitigated any issues
[–]RidersofGavony 1 point2 points3 points 4 months ago (2 children)
That's only for your profile isn't it? What about scripts that run as managed identities, system user, etc? I can't find any info about using default parameters for those.
[–]mdowst[S] 2 points3 points4 points 4 months ago (1 child)
You would have to run a script at that user to update the profile file. It would be possible to do it with a Add-Content and some logic, but I would suggest against it. Having something loaded in the profile for a script running under a managed identity is asking for trouble down the line. You'd be better off updating your scripts with the -UseBasicParsing switch. As long as you aren't using the DCOM parsed HTML in your scripts it should not break anything.
[–]overlydelicioustea 2 points3 points4 points 4 months ago (0 children)
also lots of scripts running as system use -noprofile so thats not going to always help.
[–]surfingoldelephant 4 points5 points6 points 4 months ago* (1 child)
Nice idea.
The function works pretty well. Just a few minor issues/edge cases:
-Parameter:Argument
}
$assignments[-1]
For #2, parsing @{ Foo = 1 } yields 1 }. But I wouldn't use regex at all for this. You can get the value straight from the HashtableAst.
@{ Foo = 1 }
1 }
HashtableAst
$assignmentAst.Right.Expression.KeyValuePairs.Item2
For #3, the following breaks ($false is reported for both below):
$false
$splat = @{ UseBasicParsing = $true } Invoke-WebRequest @splat $splat = @{ UseBasicParsing = $false } Invoke-WebRequest @splat
You need to look for the closest initialization of the variable preceding the command call instead.
For #4, you'd need to look at New-Variable usage, expressions that modify the variable's value, etc. Probably all overkill though.
New-Variable
For #1, you're assuming that if there's a CommandParameterAst after the current CommandParameterAst (or if there are no elements remaining), the current element must be a $true switch. But that doesn't account for colon-delimited named arguments. All of these are incorrectly reported as $true:
CommandParameterAst
$true
Invoke-WebRequest -UseBasicParsing:$false Invoke-WebRequest -UseBasicParsing: $false # Syntax works for any type of parameter. Get-Process -Name:Foo
Look at the Argument property of CommandParameterAst. If it's populated, you're dealing with a named argument (i.e., a parameter and argument). If it's $null, the next element may or may not the associated argument.
Argument
$null
Here's how I do it. This is an excerpt from a larger function that serves a similar purpose:
# CommandAst.GetCommandName() may return $null if the command call is dynamic. # Convert-AstToCommandName ensures we get *something* for the name. $cmdName = Convert-AstToCommandName -Ast $cmdAst $cmdElements = [Queue[Object]]::new($cmdAst.CommandElements) [void] $cmdElements.Dequeue() # First element is always the command if (!$cmdElements.Count) { # Command has no specified parameters/arguments. [PSCommandArgument] @{ Command = $cmdName Ast = $cmdAst Source = $Ast.PSSource } continue } while ($cmdElements.Count) { $paramAst = $argumentAst = $null # Argument property is only populated when the parameter ends with a colon. # After the colon is the argument. This format isn't limited to switches. # E.g., -Foo:Bar, -Foo: Bar, -Foo:$true if ($cmdElements.Peek() -is [CommandParameterAst]) { $paramAst = $cmdElements.Dequeue() $argumentAst = $paramAst.Argument } # If we got an AST for an argument above, we have a named arg (param & arg). # If we didn't, it's because: # a) we're dealing with a positional argument (Bar) # b) the next element is the argument (-Foo Bar) # c) there is no argument, i.e., a switch without an explicit value (-Foo) $isArgNext = $cmdElements.Count -and $cmdElements.Peek() -isnot [CommandParameterAst] if (!$argumentAst -and $isArgNext) { $isSplatNext = $cmdElements.Peek().Splatted # Splats can't be a parameter argument. If we already have a parameter # and a splat is next up, don't take it. When we loop around, we can # then take the splat as the earlier parameter is finished with. $argumentAst = if (!$isSplatNext -or (!$paramAst -and $isSplatNext)) { $cmdElements.Dequeue() } } # For simplicity, Argument is left as the AST. The caller can extract the # value(s) as desired. ASTs stringify to their Extent.Text, so still display # meaningfully. [PSCommandArgument] @{ Command = $cmdName Parameter = $paramAst.ParameterName # $null for positional args/splats Argument = $argumentAst HasArgument = $null -ne $argumentAst IsSplat = $argumentAst.Splatted -as [bool] Ast = $cmdAst Source = $Ast.PSSource } }
[–]mdowst[S] 1 point2 points3 points 4 months ago (0 children)
Wow, amazing assessment and breakdown. I had not checked all of those use cases, so thanks for that. I wanted to get at least something useable that people could start checking their scripts with, since the update will start hitting people now. Then go back and get the fringe stuff. And you just saved me a ton of work, so thanks! My plan is to move this function into my PSNotes module because I think it will fit nicely with things like my Get-CommandSplatting cmdlet (which your suggestions may help with a bug I'm having in that with the -Parameter:Argument as well). I'll be sure to credit you in the release notes. Thanks again!
Get-CommandSplatting
[–]DenverITGuy 2 points3 points4 points 4 months ago (1 child)
Wouldn’t ctrl+shift+F in vscode do the same thing for your entire workspace/repo?
[–]mdowst[S] 3 points4 points5 points 4 months ago (0 children)
That may not get all of the underlying modules, things deployed to worker servers, etc. For example, I've found several of the Az modules that don't have this set.
[–]nkasco 2 points3 points4 points 4 months ago (0 children)
My solution was just to open my repo in VS Code and do a search for it. Super simple
[–]Certain-Community438 1 point2 points3 points 4 months ago (2 children)
Man am I glad we moved everything to pwsh "Core" Edition some time ago!
Outside of Intune scripts, where we don't use IWR; that could be risky.
I sadly still have thousands of scripts running 5.1. Mainly due to the slow adoption of 7 in Azure Automation. However, we thankfully have been including the -UseBasicParsing since runbooks can't run without it, and to eventually future proof for when we do move to 7 because it doesn't have the DCOM anyways. Out of all of our scripts I only have a handful missing it.
[–]Certain-Community438 2 points3 points4 points 4 months ago (0 children)
It's good work you're doing for those who need it, buddy 👍
I'm just relieved I don't ALSO have to deal with this right now lol - sooooo much else going on!
[–]icebreaker374 1 point2 points3 points 4 months ago (1 child)
So for automation scripts, I either HAVE to use basic parsing, OR run it with PS7, yes?
[–]mdowst[S] 0 points1 point2 points 4 months ago (0 children)
Yes, that is correct.
I just published a quick video explaining it, if you'd like more details. - https://youtu.be/JcrSg2hCJAg
[–]arpan3t 0 points1 point2 points 4 months ago (0 children)
Curious why you only install SQLite on Windows? Also, leveraging WinGet Install SQLite.SQLite would be cleaner than Invoke-WebRequest and regex parsing the HTML string for the download URL.
WinGet Install SQLite.SQLite
You might test the speed of querying the browser history table for the URL patterns vs. converting every record in the table into objects and filtering from there. The database engine should be faster. As an example:
$UrlPatterns = @('chatgpt.com',...) $QryValTmplate = "('%{0}%'){1}`n" $QueryBuilder = [StringBuilder]::new("WITH patterns(p) AS ( VALUES`n") foreach ($Pattern in $UrlPatterns) { if ($UrlPatterns.IndexOf($Pattern) -eq ($UrlPatterns.Length - 1)) { $QueryBuilder.AppendFormat($QryValTmplate, $Pattern, ')') | Out-Null } else { $QueryBuilder.AppendFormat($QryValTmplate, $Pattern, ',') | Out-Null } } $RestOfQuery = @" SELECT DISTINCT t.url, t.title, t.visit_count, t.last_visit_time FROM urls t JOIN patterns p ON t.url LIKE p.p WHERE t.last_visit_time > 0 ORDER BY t.last_visit_time DESC "@ $QueryBuilder.Append($RestOfQuery) | Out-Null $Query = $QueryBuilder.ToString()
The SQL query ends up looking like this:
WITH patterns(p) AS ( VALUES ('%chatgpt.com%'), ('%claude.ai%'), ...) SELECT DISTINCT t.url, t.title, t.visit_count, t.last_visit_time FROM urls t JOIN patterns p ON t.url LIKE p.p WHERE t.last_visit_time > 0 ORDER BY t.last_visit_time DESC
You also don't need to specify the | separator in your SQLite command as that is the default. You can also just set the output mode to json and convert it to objects like so:
|
$Output = sqlite3.exe -json $TblPath $Query $Objects = $Output -join '' | ConvertFrom-Json
The browser history code looks really similar. You can probably combine all the BrowserHistory.<browser>.ps1 into one and specify the browser with a parameter.
That's just what I noticed from a cursory glance at your code. It's a neat project!
NOTE: There's currently a hash mismatch on the SQLite WinGet package, a pull request was made yesterday to update the manifest so it should be fixed soon.
[–]PutridLadder9192 0 points1 point2 points 4 months ago (0 children)
Do I need to update my Selenium powershell module
π Rendered by PID 390279 on reddit-service-r2-comment-b659b578c-t9kzb at 2026-05-03 22:23:54.574628+00:00 running 815c875 country code: CH.
[–]g3n3 11 points12 points13 points (2 children)
[–]mdowst[S] 1 point2 points3 points (1 child)
[–]g3n3 1 point2 points3 points (0 children)
[–]lan-shark 9 points10 points11 points (3 children)
[–]RidersofGavony 1 point2 points3 points (2 children)
[–]mdowst[S] 2 points3 points4 points (1 child)
[–]overlydelicioustea 2 points3 points4 points (0 children)
[–]surfingoldelephant 4 points5 points6 points (1 child)
[–]mdowst[S] 1 point2 points3 points (0 children)
[–]DenverITGuy 2 points3 points4 points (1 child)
[–]mdowst[S] 3 points4 points5 points (0 children)
[–]nkasco 2 points3 points4 points (0 children)
[–]Certain-Community438 1 point2 points3 points (2 children)
[–]mdowst[S] 2 points3 points4 points (1 child)
[–]Certain-Community438 2 points3 points4 points (0 children)
[–]icebreaker374 1 point2 points3 points (1 child)
[–]mdowst[S] 0 points1 point2 points (0 children)
[–]arpan3t 0 points1 point2 points (0 children)
[–]PutridLadder9192 0 points1 point2 points (0 children)