Public/Save-VcRedist.ps1

function Save-VcRedist {
    <#
        .EXTERNALHELP VcRedist-help.xml
    #>

    [Alias("Get-VcRedist")]
    [CmdletBinding(SupportsShouldProcess = $true, HelpURI = "https://vcredist.com/save-vcredist/")]
    [OutputType([System.Management.Automation.PSObject])]
    param (
        [Parameter(
            Mandatory = $true,
            Position = 0,
            ValueFromPipeline = $true,
            HelpMessage = "Pass a VcList object from Get-VcList.")]
        [ValidateNotNullOrEmpty()]
        [System.Management.Automation.PSObject] $VcList,

        [Parameter(Mandatory = $false, Position = 1)]
        [ValidateNotNullOrEmpty()]
        [System.String] $Path = (Resolve-Path -LiteralPath $PWD),

        [Parameter(Mandatory = $false, Position = 3)]
        [System.String] $Proxy,

        [Parameter(Mandatory = $false, Position = 4)]
        [System.Management.Automation.PSCredential]
        $ProxyCredential = [System.Management.Automation.PSCredential]::Empty,

        [Parameter(Mandatory = $false, Position = 5)]
        [ValidateNotNullOrEmpty()]
        [System.String] $UserAgent = (Get-VcUserAgent),

        [Parameter(Mandatory = $false)]
        [System.Management.Automation.SwitchParameter] $ShowProgress,

        [Parameter(Mandatory = $false)]
        [System.Management.Automation.SwitchParameter] $SkipAuthenticodeValidation
    )

    begin {
        $DownloadTimeout = [System.Int32](Get-VcConfig).Download.TimeoutSeconds
        $ShowProgressBar = $PSBoundParameters.ContainsKey("Verbose") -or $PSBoundParameters.ContainsKey("ShowProgress")

        # Create the target path if it does not exist; fail before any download if it is not a usable folder
        $FullPath = $PSCmdlet.GetUnresolvedProviderPathFromPSPath($Path)
        if (Test-Path -LiteralPath $FullPath -PathType "Leaf") {
            throw [System.IO.IOException]::New("The path '$FullPath' is a file, not a folder.")
        }
        if (-not (Test-Path -LiteralPath $FullPath -PathType "Container")) {
            if ($PSCmdlet.ShouldProcess($FullPath, "Create")) {
                try {
                    New-Item -Path $FullPath -ItemType "Directory" -Force -ErrorAction "Stop" > $null
                }
                catch {
                    throw [System.IO.DirectoryNotFoundException]::New("Unable to create the path '$FullPath': $($_.Exception.Message)")
                }
            }
        }
    }

    process {
        foreach ($VcRedist in $VcList) {
            # Only download from HTTPS URIs on the allowed host list
            if (-not (Test-VcDownloadUri -Uri $VcRedist.URI)) {
                throw [System.Management.Automation.ValidationMetadataException]::New("The URI is not HTTPS or not on the allowed host list: '$($VcRedist.URI)'.")
            }

            # Build the path to save the VcRedist into; Create the folder to store the downloaded file. Skip if it exists
            $TargetDirectory = [System.IO.Path]::Combine($FullPath, $VcRedist.Release, $VcRedist.Version, $VcRedist.Architecture)
            Write-Verbose -Message "Test directory exists: $TargetDirectory."
            if (Test-Path -LiteralPath $TargetDirectory) {
                Write-Verbose -Message "Directory exists: $TargetDirectory. Skipping."
            }
            else {
                if ($PSCmdlet.ShouldProcess($TargetDirectory, "Create")) {
                    New-Item -Path $TargetDirectory -ItemType "Directory" -Force -ErrorAction "Stop" > $null
                }
            }

            # Test whether the VcRedist is already on disk
            $TargetVcRedist = Join-Path -Path $TargetDirectory -ChildPath $(Split-Path -Path $VcRedist.URI -Leaf)
            Write-Verbose -Message "Testing for downloaded VcRedist: $($TargetVcRedist)"

            $DownloadRequired = $true
            if (Test-Path -LiteralPath $TargetVcRedist -PathType "Leaf") {
                $ProductVersion = $null
                $ExistingVersion = (Get-Item -LiteralPath $TargetVcRedist).VersionInfo.ProductVersion
                if (-not [System.Version]::TryParse($ExistingVersion, [ref] $ProductVersion) -or ([System.Version]$VcRedist.Version -gt $ProductVersion)) {
                    Write-Verbose -Message "Manifest version: '$($VcRedist.Version)' > file version: '$ExistingVersion'."
                }
                else {
                    Write-Verbose -Message "Manifest version: '$($VcRedist.Version)' matches file version: '$ExistingVersion'."
                    $DownloadRequired = $false
                }
            }

            if ($DownloadRequired -eq $true) {
                if ($PSCmdlet.ShouldProcess($VcRedist.URI, "Download")) {
                    # Download to a temporary file, verify, then move into place so a failed download never leaves a partial installer
                    $TempFile = "$TargetVcRedist.$([System.Guid]::NewGuid().Guid).download"
                    try {
                        Write-Verbose -Message "Download VcRedist: '$($VcRedist.Name) $($VcRedist.Version) $($VcRedist.Architecture)'"
                        $iwrParams = @{
                            Uri          = $VcRedist.URI
                            OutFile      = $TempFile
                            UserAgent    = $UserAgent
                            TimeoutSec   = $DownloadTimeout
                            ShowProgress = $ShowProgressBar
                        }
                        if ($PSBoundParameters.ContainsKey("Proxy")) { $iwrParams.Proxy = $Proxy }
                        if ($PSBoundParameters.ContainsKey("ProxyCredential")) { $iwrParams.ProxyCredential = $ProxyCredential }
                        Invoke-VcWebRequest @iwrParams | Out-Null

                        if (-not $SkipAuthenticodeValidation) {
                            Assert-VcSignature -Path $TempFile | Out-Null
                        }
                        else {
                            Write-Verbose -Message "Skipping Authenticode validation: $TargetVcRedist."
                        }
                        Move-Item -LiteralPath $TempFile -Destination $TargetVcRedist -Force -ErrorAction "Stop"
                    }
                    catch {
                        throw $_
                    }
                    finally {
                        if (Test-Path -LiteralPath $TempFile) { Remove-Item -LiteralPath $TempFile -Force -ErrorAction "SilentlyContinue" }
                    }
                }
                else {
                    continue
                }
            }
            else {
                Write-Verbose -Message "VcRedist exists: $TargetVcRedist."
                if (-not $SkipAuthenticodeValidation) {
                    Assert-VcSignature -Path $TargetVcRedist | Out-Null
                }
                else {
                    Write-Verbose -Message "Skipping Authenticode validation: $TargetVcRedist."
                }
            }

            # Return the $VcList object on the pipeline with the Path property pointing to the downloaded file
            Write-Verbose -Message "Add Path property: $TargetVcRedist"
            $VcRedist | Add-Member -MemberType "NoteProperty" -Name "Path" -Value $TargetVcRedist -Force
            Write-Output -InputObject $VcRedist
        }
    }

    end { }
}