functions/PowerShellGet/Install-PSFPowerShellGet.ps1

function Install-PSFPowerShellGet {
    <#
    .SYNOPSIS
        Deploys the different versions of PowerShellGet and PSResourceGet.
     
    .DESCRIPTION
        Deploys the different versions of PowerShellGet and PSResourceGet.
        With this command you can bulk-deploy PowerShell package management at scale.
 
        It can install:
        + latest version of PowerShellGet & PackageManagement (elsewhere referred to as V2/classic)
        + binaries needed to use PowerShellGet & PackageManagement without bootstrapping from the internet
        + latest version of Microsoft.PowerShell.PSResourceget (elsewhere referred to as V3/modern)
 
        It can do all that via PSRemoting, no SMB access needed.
        This command needs no internet access to deploy them - you can transport it into an offline environment and still profit from that.
     
    .PARAMETER Type
        What should be deployed/installed.
        + V2Binaries: What is required to use Get V2.
        + V2Latest: The latest version of Get V2
        + V3Latest: The latest version of Get V3
        Defaults to: V2Binaries
     
    .PARAMETER ComputerName
        The computer(s) to install to.
        Can be names, ADComputer objects, SQL Server connection strings or alreadya established PSSessions.
        Defaults to: localhost
     
    .PARAMETER Credential
        Credentials to use for establishing new remoting connections.
     
    .PARAMETER SourcePath
        Custom Path to get the module sources to deplo.
        You can download the latest module & binary versions from an online machine and then transport them into an offline environment.
        This allows you to update the version of Get V3 being deployed, without having to update (or wait for an update) of PSFramework.NuGet.
     
    .PARAMETER Offline
        Force a full offline mode.
        By default, the module will on install automatically try to check online for a newer version.
        It will still continue anyway if this fails, but if you want to avoid the network traffic & signals, use this switch.
     
    .PARAMETER NotInternal
        Do not use the internally provided PowerShellGet module versions.
        This REQUIRES you to either provide the module data via -SourcePath or to have live online access.
     
    .EXAMPLE
        PS C:\> Install-PSFPowerShell -Type V3Latest -ComputerName (Get-ADComputer -Filter * -SearchBase $myOU)
         
        This will install the latest version of PSResourceGet (V3) on all computers under the OU distinguishedName stored in $myOU
    #>

    [CmdletBinding()]
    Param (
        [ValidateSet('V2Binaries', 'V2Latest', 'V3Latest')]
        [string[]]
        $Type = 'V2Binaries',

        [Parameter(ValueFromPipeline = $true)]
        [PSFComputer[]]
        $ComputerName = $env:COMPUTERNAME,

        [PSCredential]
        $Credential,

        [string]
        $SourcePath = (Join-Path -Path (Get-PSFPath -Name AppData) -ChildPath 'PowerShell/PSFramework/modules/PowerShellGet'),

        [switch]
        $Offline,

        [switch]
        $NotInternal
    )
    
    begin {
        #region Functions
        function Resolve-PowerShellGet {
            [OutputType([hashtable])]
            [CmdletBinding()]
            param (
                [string]
                $Type,

                [string]
                $SourcePath,

                [switch]
                $Offline,

                [switch]
                $NotInternal
            )

            #region V2Binaries
            if ('V2Binaries' -eq $Type) {
                @{
                    Type    = $Type
                    NuGet   = [System.IO.File]::ReadAllBytes("$script:ModuleRoot\bin\NuGet.exe")
                    PkgMgmt = [System.IO.File]::ReadAllBytes("$script:ModuleRoot\bin\Microsoft.PackageManagement.NuGetProvider.dll")
                }
                return
            }
            #endregion V2Binaries

            $internalVersion = Get-Content -Path "$script:ModuleRoot\modules\modules.json" | ConvertFrom-Json
            if ($NotInternal) { $internalVersion = @{ } }
            $sourceVersion = @{ }
            $onlineVersion = @{ }
            $sourceFile = Join-Path -Path $SourcePath -ChildPath modules.json
            if (Test-Path -Path $sourceFile) {
                $sourceVersion = Get-Content -Path $sourceFile | ConvertFrom-Json
            }

            #region Check Online
            if (-not $Offline) {
                $links = @(
                    'PSGetV2'
                    'PSGetV3'
                    'PSPkgMgmt'
                )

                foreach ($link in $links) {
                    $resolvedUrl = Resolve-AkaMsLink -Name $link
                    if (-not $resolvedUrl) { continue }

                    $onlineVersion[$link] = [PSCustomObject]@{
                        Type     = $link
                        Name     = ($resolvedUrl -split '/')[-2]
                        Version  = ($resolvedUrl -split '/')[-1]
                        Resolved = $resolvedUrl
                        FileName = ''
                    }
                    $onlineVersion[$link].FileName = '{0}-{1}.zip' -f $onlineVersion[$link].Name, $onlineVersion[$link].Version
                }
            }
            #endregion Check Online
        
            $source = 'Internal'
            $typeTag = switch ($Type) {
                'V2Latest' { 'PSGetV2' }
                'V3Latest' { 'PSGetV3' }
            }
            if ($sourceVersion.$typeTag.Version -and $sourceVersion.$typeTag.Version -ne $internalVersion.$typeTag.Version) {
                $source = 'Source'
            }
            if ($onlineVersion.$typeTag.Version -and $onlineVersion.$typeTag.Version -ne $internalVersion.$typeTag.Version) {
                $source = 'Online'
            }
            
            # If online version is newer than internal, download to appdata as cached version
            if ('Online' -eq $source) {
                if (-not (Test-Path -Path $SourcePath)) { $null = New-Item -Path $SourcePath -ItemType Directory -Force }
                Save-PSFPowerShellGet -Path $SourcePath # This can never happen if the user specified a path, so no risk of overwriting.
            }

            $rootPath = switch ($source) {
                Internal { "$script:ModuleRoot\modules" }
                Source { $SourcePath }
                Online { $SourcePath }
            }

            $actualConfiguration = Import-PSFPowerShellDataFile -Path (Join-Path -Path $rootPath -ChildPath 'modules.json')
            $data = @{
                Type = $Type
                Config = $actualConfiguration
            }
            switch ($Type) {
                'V2Latest' {
                    $data.PSGetV2 = [System.IO.File]::ReadAllBytes((Join-Path -Path $rootPath -ChildPath $actualConfiguration.PSGetV2.FileName))
                    $data.PSPkgMgmt = [System.IO.File]::ReadAllBytes((Join-Path -Path $rootPath -ChildPath $actualConfiguration.PSPkgMgmt.FileName))
                }
                'V3Latest' {
                    $data.PSGetV3 = [System.IO.File]::ReadAllBytes((Join-Path -Path $rootPath -ChildPath $actualConfiguration.PSGetV3.FileName))
                }
            }
            $data
        }
        #endregion Functions

        #region Actual Code
        $code = {
            param (
                $Data
            )

            #region Functions
            function Install-ZipModule {
                [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "")]
                [CmdletBinding()]
                param (
                    $Config,

                    [string]
                    $ModulesFolder,

                    [string]
                    $TempFolder
                )

                $modulePath = Join-Path -Path $ModulesFolder -ChildPath ('{0}/{1}' -f $Config.Name, ($Config.Version -replace '\-.+$'))

                if (Test-Path -Path $modulePath) { return }

                $null = New-Item -Path $modulePath -ItemType Directory -Force
                Expand-Archive -Path (Join-Path -Path $TempFolder -ChildPath $Config.FileName) -DestinationPath $modulePath
            }
            #endregion Functions

            ## Create temporary folder
            $tempFolder = (New-Item -Path $env:TEMP -Name "PSGet-$(Get-Random)" -ItemType Directory -Force).FullName

            #region Write binary data
            if ($Data.NuGet) {
                [System.IO.File]::WriteAllBytes((Join-Path -Path $tempFolder -ChildPath 'NuGet.exe'), $Data.NuGet)
            }
            if ($Data.PkgMgmt) {
                [System.IO.File]::WriteAllBytes((Join-Path -Path $tempFolder -ChildPath 'Microsoft.PackageManagement.NuGetProvider.dll'), $Data.PkgMgmt)
            }
            if ($Data.PSGetV2) {
                [System.IO.File]::WriteAllBytes((Join-Path -Path $tempFolder -ChildPath $Data.Config.PSGetV2.FileName), $Data.PSGetV2)
            }
            if ($Data.PSGetV3) {
                [System.IO.File]::WriteAllBytes((Join-Path -Path $tempFolder -ChildPath $Data.Config.PSGetV3.FileName), $Data.PSGetV3)
            }
            if ($Data.PSPkgMgmt) {
                [System.IO.File]::WriteAllBytes((Join-Path -Path $tempFolder -ChildPath $Data.Config.PSPkgMgmt.FileName), $Data.PSPkgMgmt)
            }
            #endregion Write binary data

            #region Copy to destination
            $isOnWindows = $PSVersionTable.PSVersion.Major -lt 6 -or $isWindows
            switch ($Data.Type) {
                #region V2 Bootstrap
                V2Binaries {
                    if ($isOnWindows) {
                        if (-not (Test-Path -Path "$env:ProgramFiles\Microsoft\Windows\PowerShell\PowerShellGet")) {
                            $null = New-Item -Path "$env:ProgramFiles\Microsoft\Windows\PowerShell\PowerShellGet" -ItemType Directory -Force
                        }
                        Copy-Item -Path (Join-Path -Path $tempFolder -ChildPath 'NuGet.exe') -Destination "$env:ProgramFiles\Microsoft\Windows\PowerShell\PowerShellGet" -Force
                        if (-not (Test-Path -Path "$env:ProgramFiles\PackageManagement\ProviderAssemblies\nuget\2.8.5.208")) {
                            $null = New-Item -Path "$env:ProgramFiles\PackageManagement\ProviderAssemblies\nuget\2.8.5.208" -ItemType Directory -Force
                        }
                        Copy-Item -Path (Join-Path -Path $tempFolder -ChildPath 'Microsoft.PackageManagement.NuGetProvider.dll') -Destination "$env:ProgramFiles\PackageManagement\ProviderAssemblies\nuget\2.8.5.208" -Force
                    }
                    else {
                        Copy-Item -Path (Join-Path -Path $tempFolder -ChildPath 'NuGet.exe') -Destination "$HOME/.config/powershell/powershellget" -Force
                    }
                }
                #endregion V2 Bootstrap

                #region V2 Latest
                V2Latest {
                    $modulesFolder = "$env:ProgramFiles\WindowsPowerShell\modules"
                    if (-not $isOnWindows) { $modulesFolder = "/usr/local/share/powershell/Modules" }

                    Install-ZipModule -Config $data.Config.PSGetV2 -ModulesFolder $modulesFolder -TempFolder $tempFolder
                    Install-ZipModule -Config $data.Config.PSPkgMgmt -ModulesFolder $modulesFolder -TempFolder $tempFolder
                }
                #endregion V2 Latest

                #region V3 Latest
                V3Latest {
                    $modulesFolder = "$env:ProgramFiles\WindowsPowerShell\modules"
                    if (-not $isOnWindows) { $modulesFolder = "/usr/local/share/powershell/Modules" }

                    Install-ZipModule -Config $data.Config.PSGetV3 -ModulesFolder $modulesFolder -TempFolder $tempFolder
                }
                #endregion V3 Latest
            }
            #endregion Copy to destination

            ## Cleanup
            Remove-Item -Path $tempFolder -Recurse -Force
        }
        #endregion Actual Code

        #region Resolve Source Configuration
        $stayOffline = $Offline
        $useInternal = -not $NotInternal
        if ($PSBoundParameters.Keys -contains 'SourcePath') {
            if ($PSBoundParameters.Keys -notcontains 'Offline') {
                $stayOffline = $true
            }
            if ($PSBoundParameters.Keys -notcontains 'NotInternal') {
                $useInternal = $false
            }
        }
        #endregion Resolve Source Configuration
    }
    process {
        # If installing the latest V2 modules, you'll also want the binaries needed
        if ('V2Latest' -in $Type -and 'V2Binaries' -notin $Type) {
            $Type = @($Type) + 'V2Binaries'
        }

        foreach ($typeEntry in $Type) {
            # Get Binaries / Modules to deploy
            $binaries = Resolve-PowerShellGet -Type $typeEntry -Offline:$stayOffline -SourcePath $SourcePath -NotInternal:$useInternal
    
            # Execute Deployment
            Invoke-PSFCommand -ComputerName $ComputerName -ScriptBlock $code -Credential $Credential -ArgumentList $binaries
        }
    }
    end {
        Search-PSFPowerShellGet
    }
}