Get-LatestPSMacVersion.psm1

function processVersionArray {

     #param is a string array
     param (
          [Parameter(Mandatory = $true)][string[]] $theVersionArray
     )

     #the return from invoke-restmethod is a PSCustomObject, so create a list of those so we can add items to it
     $theReturnArray = [System.Collections.Generic.List[PSCustomObject]]::new()

     #iterate through the array, get the json results, and shove them onto the list
     foreach ($item in $theVersionArray) {
          $theJsonResult = Invoke-RestMethod -Uri $item
          $theReturnArray.Add($theJsonResult)
     }
     #return the list
    return $theReturnArray
}

function downloadPackage {
    #all the params are mandatory
     param(
          [Parameter(Mandatory = $true)][string]$versionTag,
        [Parameter(Mandatory = $true)][string]$version,
          [Parameter(Mandatory = $true)][string]$cpuType
     )
    #build the download URL
     $macOSPowerShellASPKGDownloadURL = "https://github.com/PowerShell/PowerShell/releases/download/$versionTag/powershell-$version-osx-$cpuType.pkg"

    #build the output package name
    $macOSPowerShellASPKG = "powershell-$version-osx-$cpuType.pkg"

    #downlaod the package to /tmp
    Invoke-RestMethod -Uri $macOSPowerShellASPKGDownloadURL -Method Get -OutFile "/tmp/$macOSPowerShellASPKG"
}

function Get-LatestPSMacVersion {

     <#
          .SYNOPSIS
          This module makes it easier to download the latest version of PowerShell for macOS. You can download the LTS, Current, or Preview version. It also lets you download the Apple Silcon (arm64) or Intel (x64) versions
 
          .DESCRIPTION
          This module downloads the latest version of PowerShell for macOS
 
          Note that PowerShell is case insensitive, so the parameters are as well. If no parameters are specified the stable Apple Silicon version is downloaded.
 
          The parameters are:
               -PSVersion or PowerShell Version. This accepts stable, lts, preview or all, default is stable
               -CPUArch, or CPU architecture. This accepts x64 for intel, arm64 for Apple Silicon (M1/M2/A19/etc.) or all
                    default is arm64
 
          one, both, or no parameters can be used
 
          .INPUTS
          None, there's nothing you can pipe to this
 
          .OUTPUTS
          An installer package in /tmp
 
          .EXAMPLE
          Get-LatestPSMacVersion
 
          This grabs the latest stable (not LTS or Preview) version of PowerShell for your computer's CPU architectures.
          On Intel, you get that version (x64), on Apple Silicon you get that version (arm64)
 
          .EXAMPLE
          Get-LatestPSMacVersion [-PSVersion stable/lts/preview]
 
          This grabs the specificed version for your computer's CPU architecture
 
          .EXAMPLE
          Get-LatestPSMacVersion [-CPUArch x64/arm64]
 
          This grabs the stable version for the specified CPU architecture
 
          .EXAMPLE
          Get-LatestPSMacVersion [-PSVersion all]
 
          This gets all three versions of Powershell (LTS, Stable, and Preview) for your computer's CPU architecture
 
          .EXAMPLE
          Get-LatestPSMacVersion [-PSVersion all] [-CPUArch x64/arm64]
 
          This gets all three versions of Powershell (LTS, Stable, and Preview) for the specified CPU architecture
 
          .EXAMPLE
          Get-LatestPSMacVersion [-PSVersion lts/stable/preview] [-CPUArch all]
 
          This gets the specified versions of Powershell (LTS, Stable, or Preview) for both x64 and arm64
 
        .EXAMPLE
          Get-LatestPSMacVersion [-PSVersion all] [-CPUArch all]
 
          This gets all three versions of Powershell (LTS, Stable, and Preview) for both x64 and arm64
 
          .LINK
     #>


     #input params, all are strings and optional
     param(
          [Parameter(Mandatory = $false)][string]$PSVersion,
          [Parameter(Mandatory = $false)][string]$cpuARCH
     )

     #this is mostly to avoid scope issues. It's also a real help for things that are sometimes an array and sometimes a string
    #or a single json objecgt
     $PSLatestVersionInfo = $null
     $PSLatestVersionArray = $null
     $cpuARCHInfo = $null
     $PSLatestReleaseTag = $null
    $PSLatestVersion = $null

     #test the version for nothing, allowed terms, or all. if all, we build an array of URLs, if null/bad value, it's select
     switch ($PSVersion) {
          {$PSVersion -eq "stable" -or $PSVersion -eq "lts" -or $PSVersion -eq "preview"}
               {
                    #if it's one of the good options, get the json info for that version
                    $PSLatestVersionInfo = Invoke-RestMethod -Uri "https://aka.ms/pwsh-buildinfo-$PSVersion"
                break
               }

          {$PSVersion -eq "all"}
               {
                    #if it's all, build an array of json objects
                    $PSLatestVersionArray = @(
                         "https://aka.ms/pwsh-buildinfo-stable"
                         "https://aka.ms/pwsh-buildinfo-lts"
                         "https://aka.ms/pwsh-buildinfo-preview"
                    )

                    $PSLatestVersionInfo = processVersionArray -theVersionArray $PSLatestVersionArray
                    break
               }

          Default {
               #use stable
               $PSLatestVersionInfo = Invoke-RestMethod -Uri "https://aka.ms/pwsh-buildinfo-stable"
          }
     }

     #for this, we really only care about "all", null, or invalid input
     switch ($cpuARCH) {
          {$cpuARCH -eq "arm64" -or $cpuARCH -eq "x64"} {
               $cpuARCHInfo = $cpuARCH
               break
          }
          {$cpuARCH -eq "all"} {
               #if it's all, build an array
               $cpuARCHInfo = @(
                    "arm64"
                    "x64"
               )
               break
          }

          {$null -eq $cpuARCH} {
               #if they don't enter any CPU arch param, we check for the architecture on the mac this is running on
               #and set $cpuArch accordingly
               $myCPUArch = Invoke-Expression -Command "/usr/bin/uname -m"
               if ($myCPUArch -eq "x86_64") {
                    $cpuARCH = "x64"
               } else {
                    $cpuARCH = "arm64"
               }
          }

          Default {
               #arm64
               $cpuARCHInfo = "arm64"
          }
     }

     #test for version array
     if ($PSLatestVersionInfo.GetType().BaseType.Name -eq "Array") {
          #it's an array
          #set the release tags to a list of strings
          $PSLatestReleaseTag = [System.Collections.Generic.List[string]]::new()

          #get a list of tags
        foreach ($item in $PSLatestVersionInfo) {
            #get the tags
              $PSLatestReleaseTag.Add($item.ReleaseTag)
          }

        #set the version to a list of strings
        $PSLatestVersion = [System.Collections.Generic.List[string]]::new()

        #get a list of version numbers sans the leading 'v'
        foreach ($item in $PSLatestReleaseTag) {
            #strip the leading v off the tag and add it to PS latest version
            $PSLatestVersion.Add($item.Substring(1))
        }

     } else {
        #not an array

        #get the releasetag
         $PSLatestReleaseTag = $PSLatestVersionInfo.ReleaseTag

        #build the version sans the leading v
         $PSLatestVersion = $PSLatestReleaseTag.Substring(1)
    }

    #download selector. the vars we care about are either lists, arrays or strings, so we check first for neither being an array/list
    #we only have to test either releasetag or version, since both are always made lists or strings together. there's no chance
    #for one to be a different type than the other

    #because the release tag is a list and the CPUArchInfo is an array, we have to test differently
    if ((!($PSLatestReleaseTag.GetType().Name.Contains("List"))) -and ($cpuARCHInfo.GetType().BaseType.Name -ne "Array")) {
        #all the tags are strings, simplest case, call the function to download
        downloadPackage -versionTag $PSLatestReleaseTag -version $PSLatestVersion -cpuType $cpuARCHInfo

        Write-Output "PowerShell $PSLatestReleaseTag for $cpuARCHInfo downloaded to /tmp"
    } elseif ((!($PSLatestReleaseTag.GetType().Name.Contains("List"))) -and ($cpuARCHInfo.GetType().BaseType.Name -eq "Array")) {
        #the release type is NOT an array, but the CPU arch IS an array

        #we iterate through the CPUArchInfo array and run once for each
        foreach ($item in $cpuARCHInfo) {
            downloadPackage  -versionTag $PSLatestReleaseTag -version $PSLatestVersion -cpuType $item

            Write-Output "PowerShell $PSLatestReleaseTag for $item downloaded to /tmp"
        }
    } elseif (($PSLatestReleaseTag.GetType().Name.Contains("List")) -and ($cpuARCHInfo.GetType().BaseType.Name -ne "Array")) {
        #the release type IS an array, but the CPU arch is NOT an array
        #because we have two different lists, albeit identical in content,
        #we need to know the current index so both can be sync'd
        foreach ($item in $PSLatestReleaseTag) {
            #get the index of $item
            $theIndex = $PSLatestReleaseTag.IndexOf($item)

            #download the package for that version and CPU type. For this If, CPU type is a constant string
            downloadPackage -versionTag $PSLatestReleaseTag[$theIndex] -version $PSLatestVersion[$theIndex] -cpuType $cpuARCHInfo

            #this just makes the output look nicer
            $tempTag = $PSLatestReleaseTag[$theIndex]
            Write-Output "PowerShell $tempTag for $cpuARCHInfo downloaded to /tmp"
        }
    } elseif (($PSLatestReleaseTag.GetType().Name.Contains("List")) -and ($cpuARCHInfo.GetType().BaseType.Name -eq "Array")) {
          #EVERYTHING IS AN ARRAY OR A LIST
          #we'll use two loops here, the outer loop will be based on CPU type, inner loop on version
          foreach ($cpu in $cpuARCHInfo) {
               foreach ($item in $PSLatestReleaseTag) {
                    #get the index of $item
                 $theIndex = $PSLatestReleaseTag.IndexOf($item)

                    #download
                    downloadPackage -versionTag $PSLatestReleaseTag[$theIndex] -version $PSLatestVersion[$theIndex] -cpuType $cpu

                    $tempTag = $PSLatestReleaseTag[$theIndex]
                    Write-Output "PowerShell $tempTag for $cpu downloaded to /tmp"
               }
          }
     } else {
        Write-Output "Something went very wrong. Double-check your parameters and/or your internet connection"
    }
}

Export-ModuleMember -Function Get-LatestPSMacVersion

# SIG # Begin signature block
# MIIMgQYJKoZIhvcNAQcCoIIMcjCCDG4CAQMxDTALBglghkgBZQMEAgEwewYKKwYB
# BAGCNwIBBKBtBGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCDC61Nv+rzDjLQN
# pBNeg/TInAzpqGVHtQvc3vVUDXuOaKCCCawwggQEMIIC7KADAgECAggYeqmowpYh
# DDANBgkqhkiG9w0BAQsFADBiMQswCQYDVQQGEwJVUzETMBEGA1UEChMKQXBwbGUg
# SW5jLjEmMCQGA1UECxMdQXBwbGUgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxFjAU
# BgNVBAMTDUFwcGxlIFJvb3QgQ0EwHhcNMTIwMjAxMjIxMjE1WhcNMjcwMjAxMjIx
# MjE1WjB5MS0wKwYDVQQDDCREZXZlbG9wZXIgSUQgQ2VydGlmaWNhdGlvbiBBdXRo
# b3JpdHkxJjAkBgNVBAsMHUFwcGxlIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MRMw
# EQYDVQQKDApBcHBsZSBJbmMuMQswCQYDVQQGEwJVUzCCASIwDQYJKoZIhvcNAQEB
# BQADggEPADCCAQoCggEBAIl2TwZbmkHupSMrAqNf13M/wDWwi4QKPwYkf6eVP+tP
# DpOvtA7QyD7lbRizH+iJR7/XCQjk/1aYKRXnlJ25NaMKzbTA4eJg9MrsKXhFaWlg
# a1+KkvyeI+Y6wiKzMU8cuvK2NFlC7rCpAgMYkQS2s3guMx+ARQ1Fb7sOWlt/OufY
# CNcLDjJt+4Y25GyrxBGKcIQmqp9E0fG4xnuUF5tI9wtYFrojxZ8VOX7KXcMyXw/g
# Un9A6r6sCGSVW8kanOWAyh9qRBxsPsSwJh8d7HuvXqBqPUepWBIxPyB2KG0dHLDC
# ThFpJovL1tARgslOD/FWdNDZCEtmeKKrrKfi0kyHWckCAwEAAaOBpjCBozAdBgNV
# HQ4EFgQUVxftos/cfJihEOD8voctLPLjF1QwDwYDVR0TAQH/BAUwAwEB/zAfBgNV
# HSMEGDAWgBQr0GlHlHYJ/vRrjS5ApvdHTX8IXjAuBgNVHR8EJzAlMCOgIaAfhh1o
# dHRwOi8vY3JsLmFwcGxlLmNvbS9yb290LmNybDAOBgNVHQ8BAf8EBAMCAYYwEAYK
# KoZIhvdjZAYCBgQCBQAwDQYJKoZIhvcNAQELBQADggEBAEI5dGuh3MakjzcqjLMd
# CkS8lSx/vFm4rGH7B5CSMrnUvzvBUDlqRHSi7FsfcOWq3UtsHCNxLV/RxZO+7puK
# cGWCnRbjGhAXiS2ozf0MeFhJDCh/M+4Aehu0dqy2tbtP36gbncgZl0oLVmcvwj62
# s8SDOvB3bXTELiNR7pqlA29g9KVIpwbCu1riHx9GRX7kl/UnELcgInJvctrGUHXF
# PSWPXaMA6Z82jEg5j7M76pCALpWaYPR4zvQOClM+ovpP2B6uhJWNMrxWTYnpeBjg
# rJpCunpGG4Siic4U6IjRWIv2rlbELAUqRa8L2UupAg80rIjHYVWJRMkncwfuguVO
# 9XAwggWgMIIEiKADAgECAgg4/t1QcpKr9DANBgkqhkiG9w0BAQsFADB5MS0wKwYD
# VQQDDCREZXZlbG9wZXIgSUQgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxJjAkBgNV
# BAsMHUFwcGxlIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MRMwEQYDVQQKDApBcHBs
# ZSBJbmMuMQswCQYDVQQGEwJVUzAeFw0yNTA1MjAxNzI2MzZaFw0yNzAyMDEyMjEy
# MTVaMIGPMRowGAYKCZImiZPyLGQBAQwKNzk2NDg4Vkc5NTE6MDgGA1UEAwwxRGV2
# ZWxvcGVyIElEIEFwcGxpY2F0aW9uOiBKb2huIFdlbGNoICg3OTY0ODhWRzk1KTET
# MBEGA1UECwwKNzk2NDg4Vkc5NTETMBEGA1UECgwKSm9obiBXZWxjaDELMAkGA1UE
# BhMCVVMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDG5iTyi/Llb8QR
# mKSVEMaSkIAyXssehEKC1nJKp9ZkaRLxan/q6B7JeWzTNa6XCBG3Pf4Dz0IQnU6U
# qDsPFYpwhRtS14CNVnwmcMJeJ/hy6MW++cBymq9xEZD80c69muLZmYr2KLKFu6WJ
# nPK4JvYNqZ5Iug7UthcVeZBTdCDHCsVui8WMrFwDe112hqHFb9YiwrZF3w4v3G7X
# cU6KO6oiD79C26xelHqAjuPZUxHbiJDhrgI2xbr4phtyukq/aaUyvOEGRuzr9ViT
# imxjMH0Dzd3eYQNyZ/OgpBTg/u5bt4c4L+ivLNWpkImm5NLByjPWj2Rgxex9q7gu
# d8eXX5PBAgMBAAGjggITMIICDzAMBgNVHRMBAf8EAjAAMB8GA1UdIwQYMBaAFFcX
# 7aLP3HyYoRDg/L6HLSzy4xdUMEAGCCsGAQUFBwEBBDQwMjAwBggrBgEFBQcwAYYk
# aHR0cDovL29jc3AuYXBwbGUuY29tL29jc3AwMy1kZXZpZDA2MIIBHQYDVR0gBIIB
# FDCCARAwggEMBgkqhkiG92NkBQEwgf4wgcMGCCsGAQUFBwICMIG2DIGzUmVsaWFu
# Y2Ugb24gdGhpcyBjZXJ0aWZpY2F0ZSBieSBhbnkgcGFydHkgYXNzdW1lcyBhY2Nl
# cHRhbmNlIG9mIHRoZSB0aGVuIGFwcGxpY2FibGUgc3RhbmRhcmQgdGVybXMgYW5k
# IGNvbmRpdGlvbnMgb2YgdXNlLCBjZXJ0aWZpY2F0ZSBwb2xpY3kgYW5kIGNlcnRp
# ZmljYXRpb24gcHJhY3RpY2Ugc3RhdGVtZW50cy4wNgYIKwYBBQUHAgEWKmh0dHA6
# Ly93d3cuYXBwbGUuY29tL2NlcnRpZmljYXRlYXV0aG9yaXR5LzAWBgNVHSUBAf8E
# DDAKBggrBgEFBQcDAzAdBgNVHQ4EFgQU+vpKLJ3aUHMJjwgzoV2hMj3fKX8wDgYD
# VR0PAQH/BAQDAgeAMB8GCiqGSIb3Y2QGASEEEQwPMjAxOTAyMDYwMDAwMDBaMBMG
# CiqGSIb3Y2QGAQ0BAf8EAgUAMA0GCSqGSIb3DQEBCwUAA4IBAQAV1Oma8FyEr7uz
# iO/jzM9Zv+vfk6USv3P/G444JO51tYK9pfyOBIw1bAQriiW4AmUbgYsOTPBRDaSn
# MuZBi/srL1G+mXngoiqaP4sdt3MgCt/mJTmz/PBxVZgLk8XKxZR8C6GBKX9vVSTR
# oAbwake9HBhJe4RnNoELGuXk62b1QKnkKDKUizZtuAib13yDSr5bN0KxezeGmySg
# t1maDJ+qCYpQUMB3Nls7taCozh4lsl7xtvc/2A8l2Nf9pcT0GGpkZdpf1WwUqm6O
# 3fUQ7+GCO2ctz8CfUchecgcBDMP40oTKDsW7d8oLP5LU8YK8lgoAzUeHA5JMscLU
# Ud176XtfMYICKzCCAicCAQEwgYUweTEtMCsGA1UEAwwkRGV2ZWxvcGVyIElEIENl
# cnRpZmljYXRpb24gQXV0aG9yaXR5MSYwJAYDVQQLDB1BcHBsZSBDZXJ0aWZpY2F0
# aW9uIEF1dGhvcml0eTETMBEGA1UECgwKQXBwbGUgSW5jLjELMAkGA1UEBhMCVVMC
# CDj+3VBykqv0MAsGCWCGSAFlAwQCAaB8MBAGCisGAQQBgjcCAQwxAjAAMBkGCSqG
# SIb3DQEJAzEMBgorBgEEAYI3AgEEMBwGCisGAQQBgjcCAQsxDjAMBgorBgEEAYI3
# AgEVMC8GCSqGSIb3DQEJBDEiBCDr4q3x021bQtw1VbvUI2nTjiYzEtgEx3Xq9/dP
# CnbT2zALBgkqhkiG9w0BAQEEggEAVjgfd+qOxu9H8PhBPIdm1g5UFtkF/GNtW/7W
# jMNUGs/SjUt9sOBiWGMcaXnoHhzgHwVI3d+21kbu4ES6a+UQ4nExy0GnhByH1LTE
# 1KQumo0ZCXBYYBWkLKViz4cONJjOV6PGKYh66AjAcuqEtI++QvyekFF1tJyDiGjn
# XMy2ZVmFfvojNmzIuHh9nFn093Pu5pvz6HQ/ew9k3pRJfv+1Y/eljYu4dj+XMRdP
# tRiHN8JvOaXPslrZF1pqsiHZnnL2NuqAIor5VYpwJuMSrYWbdYMTqWJJ2ZL5sabP
# VxCihLrmgVkLNKzTNYFADoM7XlWH+NHlHfl/XofN+yxvPbjIRQ==
# SIG # End signature block