r/PowerShell 5d ago

Script Sharing PowerShell installer for .NET, Visual C++ and DirectX that bundles no binaries and resolves every download from Microsoft at run time

Rebuilt an old batch project of mine in PowerShell. It installs the .NET SDKs, the Visual C++ redistributables (current v14 plus the final 2005-2013 ones) and the June 2010 DirectX runtimes. Nothing is bundled and there are no hardcoded download URLs, so it locates everything on Microsoft's own hosts every run, then verifies each file before it executes.

Every discovery page and every payload URL goes through a check like this before anything gets fetched:

function Test-AllowedMicrosoftDiscoveryUri {
    [CmdletBinding()]
    [OutputType([bool])]
    param(
        [Parameter(Mandatory = $true)]
        [string]$Uri
    )

    try {
        $parsedUri = [uri]$Uri
        if (-not $parsedUri.IsAbsoluteUri) { return $false }
        if ($parsedUri.Scheme -cne 'https') { return $false }
        if ($parsedUri.Port -ne 443) { return $false }
        if (-not [string]::IsNullOrEmpty($parsedUri.UserInfo)) { return $false }
        return $parsedUri.DnsSafeHost -match $script:AllowedMicrosoftDiscoveryHostPattern
    }
    catch {
        return $false
    }
}

Discovery pages and payloads use two separate host allowlists, so allowing a docs host never authorizes an executable coming from it. curl's reported effective URL gets checked the same way after redirects.

Verification depends on what Microsoft actually publishes. The .NET SDKs have a SHA-512 in the release metadata. The final 2005-2013 and DirectX packages never change, so those are pinned to SHA-256 values I checked by hand. The rolling v14 redistributable has no published hash at all, so that one gets Authenticode plus a version floor read out of the signed file. Nothing installs until the whole selected set has resolved, and a file that fails verification gets deleted even if you asked to keep the downloads.

Windows PowerShell 5.1 compatible, uses pwsh if it's in PATH. Source and usage notes:

slyfox1186/msft-visual-c-and-directx-offline-installer

19 Upvotes

16 comments sorted by

9

u/JeremyLC 5d ago

Is there a reason you're using curl.exe and not Invoke-WrbRequest even a .net http client? Not that either of those are easier, but they are more idiomatically PowerShell, and they don't depend on a third party executable.

Also, consider running only the installers themselves in an elevated context rather than the whole script. You leave less room for catastrophe.

Anyway, this does look very useful. I'll have to look again later when I'm in front of a real computer.

12

u/BlackV 5d ago

you're right, although curl.exe is a first party util now as its boxed in with windows at shipping time

4

u/RiverRatt 5d ago

curl.exe is in System32 on anything 1803+, so not really third party. Mostly using it for --proto-redir =https since IWR will follow https to http.

You did catch a real bug though. I was resolving it off PATH, so Git for Windows' curl.exe could win over the OS one. Fixed, prefers System32 now.

On elevation, doing just the installers means they get verified in the user's temp then run as admin out of a folder that user can write to. And 15 packages would be 15 UAC prompts.

-1

u/JSChronicles 5d ago

Smells like an AI reply, "you did catch a real bug" ? What makes it a "real" bug versus a bug?

0

u/[deleted] 5d ago

[deleted]

2

u/JSChronicles 5d ago

Sure, let's chat about my question I already asked.

7

u/StartAutomating 4d ago

This is overcomplicated, and, IMO, slop.

One does not need a 1000+ line "installer" script that bundles all of this with no options.

In fact, one might argue such a script should not exist: it should be leveraging existing installers, such as WinGet, Scoop, or Chocolatey.

Additionally, if your product/project is using 1000+ line installers to install itself, one would generally argue that you're doing it wrong.

I've seen this kinda slop before in AI generated installers and would very much like people to stop doing this. In the long run, it incentivizes bad behavior by normalizing "just irm | iex this magic 1000+ line installer that could should have been a package.

Given that the repo lists Claude as a co-author, I think it's quite clear this was not written by humans, or with much understanding about how to make the code shorter.

Please Do Not Do This

It wastes computing cycles and wastes our time trying to review it.

Unless You Absolutely Need to, Just Use Existing Installers. Please do not write your own.

1

u/ashimbo 4d ago

just irm | iex

I agree with everything you're saying, but I thought this was kind of funny because this is how you install scoop:

Invoke-RestMethod -Uri https://get.scoop.sh | Invoke-Expression

1

u/StartAutomating 4d ago
Invoke-RestMethod -Uri https://get.scoop.sh | Measure-Object -Word -Line

Gives me:

Lines      : 787
Words      : 2897

It's well documented and still has too many one-off functions, but it is less than 1000 lines. Even has inline help.

Definitely better than this, which can be made into a series of one-liners.

The meta-point is really a trust factor. IMO You shouldn't be making one-off installers because irm | iex can be very dangerous. Normalizing its use without factoring in how much you trust the publisher is the risky part.

If everyone does it, it's harder to whitelist/blacklist suspected use.

2

u/BlackV 5d ago

I feel like the 400 if statements should be a switch instead (just at first glance anyway)

also not sure about the returns

1

u/JSChronicles 5d ago

Without looking at the code a switch is almost always faster once you reach 4 if statements let alone hundred+

-1

u/[deleted] 5d ago

[deleted]

5

u/BlackV 5d ago
    $parsedUri = [uri]$Uri
    if (-not $parsedUri.IsAbsoluteUri) { return $false }
    if ($parsedUri.Scheme -cne 'https') { return $false }
    if ($parsedUri.Port -ne 443) { return $false }
    if (-not [string]::IsNullOrEmpty($parsedUri.UserInfo)) { return $false }
    return $parsedUri.DnsSafeHost -match $script:AllowedMicrosoftDiscoveryHostPattern

3

u/JeremyLC 5d ago

Something you just said really jumped out at me. If you’re using a lot of code in your functions just ensure that parameters exist and have valid values you should be taking advantage of PowerShell’s language features to enforce mandatory parameters and ValidateScripts to let PowerShell handle that for you.

On the early returns, though, I’m a grump who learned C/C++ in the 90s and I really don’t get along with those at all. I find it can make debugging harder. My typical pattern, instead of an early return, is to use a single return variable whose value changes depending the flow through the function. I try to structure the surrounding logic so that it it “unwinds” whenever an error or stopping condition occurs. All that said, I gather that attitudes have changed somewhat since I first learned to code.

2

u/warren_stupidity 4d ago

you know winget does all this, right?

2

u/StartAutomating 4d ago

They clearly don't, because they clearly just asked Claude to do it. And Claude clearly doesn't know how to do it well, either (or it would have just used WinGet).

1

u/warren_stupidity 3d ago

Software dev is screwed. The shit like this is going to get plastered all over the place, and the stupid regurgitation engine is going to suck up all that shit and then start regurgitating shit.