functions/Install-App.ps1

function Install-WUApp {
    [CmdletBinding()]
    param (
        [string[]]
        $ScoopApp,

        [hashtable]
        $ScoopBucket,

        [string[]]
        $ChocolateyPackage,

        [string[]]
        $PSModule,

        [string[]]
        $Pip3Package,

        [switch]
        $Unsafe,

        [switch]
        $Force,

        [ValidateSet('All', 'Scoop', 'Chocolatey', 'PSModule', 'Pip')]
        [string[]]
        $Optimize
    )

    function Test-WUAdmin {
        (
            [Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::
            GetCurrent()
        ).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
    }

    $shouldOptimize = {
        param (
            [Parameter(Mandatory)]
            [ValidateNotNullOrEmpty()]
            [string]
            $Provider
        )

        return $Provider -in $Optimize `
            -or 'All' -in $Optimize
    }

    function Install-PowerShellModule {
        [CmdletBinding(SupportsShouldProcess)]
        param (
            [string[]]
            $PSModule,

            [switch]
            $Force,

            [switch]
            $Optimize
        )

        if ($Optimize -or $PSModule) {
            # PowerShell Gallery
            if (!(Get-PackageProvider | Where-Object Name -EQ 'NuGet')) {
                Install-PackageProvider -Name 'NuGet' -Force -Scope CurrentUser
            }
            if (!(Get-PSRepository | Where-Object { $_.Name -eq 'PSGallery' -and $_.InstallationPolicy -eq 'Trusted' })) {
                Set-PSRepository -Name 'PSGallery' -InstallationPolicy 'Trusted'
            }

            # Update PowerShellGet Module
            if (@(Get-Module 'PowerShellGet' -ListAvailable).Count -eq 1) {
                Install-Module -Name PowerShellGet -Force -AllowClobber -Scope CurrentUser
                Update-Module -Name PowerShellGet
            }

            $PSModule |
            Where-Object { $_ } |
            ForEach-Object {
                if ($Force) {
                    Install-Module -Name $_ -Scope CurrentUser -Force -AllowClobber
                }
                else {
                    Install-Module -Name $_ -Scope CurrentUser
                }
            }
        }
    }

    function Install-Scoop {
        [CmdletBinding(SupportsShouldProcess)]
        param (
            [string[]]
            $ScoopApp,

            [hashtable]
            $ScoopBucket,

            [switch]
            $Unsafe,

            [switch]
            $Force,

            [switch]
            $Optimize
        )

        function Get-InstalledScoopApp {
            $installedApps = @()
            $appListStrings = @()
            $appListStrings += scoop list 2>&1 6>&1 | ForEach-Object ToString

            $labelList = @{
                1 = 'name'
                2 = 'version'
                3 = 'bucketName'
                4 = 'isFailed'
            }

            for (($i = 1), ($labelId = 1); $i -lt $appListStrings.Count - 2; $i++, $labelId++) {
                if ($labelId -eq 4) {
                    $labelId = 0
                    continue
                }

                switch ($labelId) {
                    1 {
                        $installedApps += [PSCustomObject]@{}
                        $appListStrings[$i] = $appListStrings[$i] -replace '^\s*|\s*$'
                    }
                    3 {
                        if ($appListStrings[$i] -match '\*failed\*') {
                            $isFailed = $true

                            $installJsonPath = $env:SCOOP |
                            Join-Path -ChildPath ('apps\{0}\current\install.json' -f $installedApps[$installedApps.Count - 1].name)

                            $bucket = ''
                            $bucket = $installJsonPath |
                            Where-Object { Test-Path -LiteralPath $_ } |
                            Get-Content -LiteralPath { $_ } |
                            ConvertFrom-Json -ErrorAction Ignore |
                            Select-Object -ExpandProperty bucket -ErrorAction Ignore
                            $appListStrings[$i] = $bucket
                        }
                        else {
                            $isFailed = $false

                            $appListStrings[$i] = $appListStrings[$i] -replace '^\s*\[|\]$'
                        }

                        $installedApps[$installedApps.Count - 1] | Add-Member -MemberType NoteProperty -Name $labelList[4] -Value $isFailed
                    }
                }

                $installedApps[$installedApps.Count - 1] | Add-Member -MemberType NoteProperty -Name $labelList[$labelId] -Value $appListStrings[$i]
            }

            return $installedApps
        }

        function Install-ScoopApp {
            [CmdletBinding(SupportsShouldProcess)]
            param (
                [string[]]
                $ScoopApp,

                [switch]
                $Unsafe,

                [switch]
                $Force
            )

            $ScoopApp |
            Where-Object { $_ } |
            ForEach-Object {
                $cmd = 'scoop install "{0}"' -f $_
                if ($Force) {
                    $cmd = '{0} --force' -f $cmd
                }
                if ($Unsafe) {
                    $cmd = '{0} --skip' -f $cmd
                }
                Invoke-Expression $cmd
            }
        }

        if ($Optimize -or $ScoopApp -or $ScoopBucket) {
            # Install Scoop
            if (!(Get-Command -Name 'scoop' -ErrorAction Ignore)) {
                Invoke-WebRequest -useb get.scoop.sh | Invoke-Expression
            }

            # Set process environment variable 'SCOOP'
            $env:SCOOP = Get-Command -Name 'scoop' |
            Select-Object -ExpandProperty Path |
            Split-Path -Parent |
            Split-Path -Parent

            # Install depends
            if (!(Get-Command -Name 'git.exe' -ErrorAction Ignore)) {
                Install-ScoopApp -ScoopApp 'git' -Unsafe:$Unsafe -Force:$Force

                if (!(Get-Command -Name 'git.exe' -ErrorAction Ignore)) {
                    # If git installation fails, uninstall it and try installing again
                    Get-InstalledScoopApp |
                    Where-Object { $_.name -eq 'git' } |
                    Where-Object { $_.isFailed -eq $true } |
                    ForEach-Object {
                        scoop uninstall $_.name
                        Install-ScoopApp -ScoopApp $_.name -Unsafe:$Unsafe -Force:$Force
                    }
                }
                if (!(Get-Command -Name 'git.exe' -ErrorAction Ignore)) {
                    # Try to avoid Scoop updates and try to install in case Git is required to update Scoop
                    $currentLastUpdate = scoop config lastupdate
                    $newLastUpdate = $currentLastUpdate -replace '\|.+', ('|{0}' -f (Get-Date -Format "yyyy-MM-dd HH:mm:ss"))
                    scoop config lastupdate $newLastUpdate
                    Install-ScoopApp -ScoopApp 'git' -Unsafe:$Unsafe -Force:$Force
                    scoop update
                }
            }

            @(
                @{
                    CmdName = '7z.exe'
                    AppName = '7zip'
                }
            ) |
            Where-Object { !(Get-Command -Name $_.CmdName -ErrorAction Ignore) } |
            ForEach-Object {
                Install-ScoopApp -ScoopApp $_.AppName -Unsafe:$Unsafe -Force:$Force
            }

            if ($Optimize) {
                # Set user environment variable 'SCOOP'
                if (![Environment]::GetEnvironmentVariable('SCOOP', 'User')) {
                    [Environment]::SetEnvironmentVariable('SCOOP', $env:SCOOP, 'User')
                }

                # Set Scoop Repo to Shovel
                $scoopRepo = 'https://github.com/Ash258/Scoop-Core'
                if ((scoop config SCOOP_REPO) -ne $scoopRepo) {
                    scoop config SCOOP_REPO $scoopRepo
                    scoop update
                }

                # Register Shovel executables
                if (!(Get-Command -Name 'shovel' -ErrorAction Ignore)) {
                    Join-Path $env:SCOOP 'shims' |
                    Get-ChildItem -LiteralPath { $_ } -Filter 'scoop.*' |
                    Copy-Item -Destination {
                        Join-Path $_.Directory.FullName (($_.BaseName -replace 'scoop', 'shovel') + $_.Extension)
                    }
                }

                # Install the apps recommended by Scoop
                @(
                    @{
                        CmdName = 'aria2c.exe'
                        AppName = 'aria2'
                    }
                    @{
                        CmdName = 'innounp.exe'
                        AppName = 'innounp'
                    }
                    @{
                        CmdName = 'lessmsi.exe'
                        AppName = 'lessmsi'
                    }
                ) |
                Where-Object { !(Get-Command -Name $_.CmdName -ErrorAction Ignore) } |
                ForEach-Object {
                    Install-ScoopApp -ScoopApp $_.AppName -Unsafe:$Unsafe -Force:$Force
                }

                # Enable MSIEXTRACT_USE_LESSMSI by default
                if ((scoop config MSIEXTRACT_USE_LESSMSI) -eq "'MSIEXTRACT_USE_LESSMSI' is not set") {
                    scoop config MSIEXTRACT_USE_LESSMSI $true
                }
            }

            # Install Scoop buckets
            $installedBucketNames = scoop bucket list
            [string[]]$ScoopBucket.Keys |
            Where-Object { $_ } |
            Where-Object { !($_ -in $installedBucketNames) } |
            ForEach-Object {
                $aBucketName = $_
                if ($ScoopBucket.$aBucketName) {
                    scoop bucket add $aBucketName $ScoopBucket.$aBucketName
                }
                else {
                    scoop bucket add $aBucketName
                }
            }

            # Install Scoop apps
            Install-ScoopApp -ScoopApp $ScoopApp -Unsafe:$Unsafe -Force:$Force

            if ($Optimize) {
                # Reinstall the failed apps
                Get-InstalledScoopApp |
                Where-Object { $_.isFailed -eq $true } |
                ForEach-Object {
                    $aFullName = '{0}/{1}' -f $_.bucketName, $_.name
                    scoop uninstall $aFullName
                    Install-ScoopApp -ScoopApp $aFullName -Unsafe:$Unsafe -Force:$Force
                }
            }
        }
    }

    function Install-Chocolatey {
        [CmdletBinding(SupportsShouldProcess)]
        param (
            [string[]]
            $ChocolateyPackage,

            [switch]
            $Unsafe,

            [switch]
            $Force,

            [switch]
            $Optimize
        )

        if ($Optimize -or $ChocolateyPackage) {
            if (!(Test-WUAdmin)) {
                Write-Warning "Administrator privileges are required for Chocolatey."
                return
            }

            if (!(Get-Command -Name chocolatey -ErrorAction Ignore)) {
                # Prevent function name conflicts
                $PSModuleAutoloadingPreference = 'ModuleQualified'
                $moduleNames = @(
                    'Carbon'
                )
                Remove-Module $moduleNames -ErrorAction Ignore

                # Install Chocolatey
                Invoke-WebRequest https://chocolatey.org/install.ps1 -UseBasicParsing | Invoke-Expression

                $PSModuleAutoloadingPreference = $null
            }

            # Set chocolatey configs
            if ($Optimize) {
                if ($Unsafe) {
                    # Disable confirm script execution
                    choco feature enable -n allowGlobalConfirmation
                    # Disable checksum
                    choco feature disable -n checksumFiles
                }
            }

            # Install Chocolatey apps
            $ChocolateyPackage |
            Where-Object { $_ } |
            ForEach-Object {
                $cmd = 'choco install "{0}" --limitoutput --yes' -f $_
                if ($Force) {
                    $cmd = '{0} --force' -f $cmd
                }
                if ($Unsafe) {
                    $cmd = '{0} --ignore-checksums' -f $cmd
                }
                Invoke-Expression $cmd
            }
        }
    }

    function Install-Pip3 {
        [CmdletBinding(SupportsShouldProcess)]
        param (
            [string[]]
            $Pip3Package,

            [switch]
            $Force,

            [switch]
            $Optimize
        )

        if ($Optimize -or $Pip3Package) {
            if (!(Get-Command -Name pip3 -ErrorAction Ignore)) {
                Install-Scoop -ScoopApp 'python'
            }

            $Pip3Package |
            Where-Object { $_ } |
            ForEach-Object {
                if ($Force) {
                    pip3 install --upgrade --force-reinstall $_
                }
                else {
                    pip3 install --upgrade $_
                }
            }
        }
    }

    $params = @{} + $PSBoundParameters
    $keyNames = @(
        'PSModule'
        'Force'
    )
    $removeKeyNames = $params.Keys | Where-Object { !($_ -in $keyNames) }
    $removeKeyNames | ForEach-Object { $params.Remove($_) }
    $params.Optimize = & $shouldOptimize -Provider 'PSModule'
    Install-PowerShellModule @params

    $params = @{} + $PSBoundParameters
    $keyNames = @(
        'ScoopApp'
        'ScoopBucket'
        'Unsafe'
        'Force'
    )
    $removeKeyNames = $params.Keys | Where-Object { !($_ -in $keyNames) }
    $removeKeyNames | ForEach-Object { $params.Remove($_) }
    $params.Optimize = & $shouldOptimize -Provider 'Scoop'
    Install-Scoop @params

    $params = @{} + $PSBoundParameters
    $keyNames = @(
        'ChocolateyPackage'
        'Unsafe'
        'Force'
    )
    $removeKeyNames = $params.Keys | Where-Object { !($_ -in $keyNames) }
    $removeKeyNames | ForEach-Object { $params.Remove($_) }
    $params.Optimize = & $shouldOptimize -Provider 'Chocolatey'
    Install-Chocolatey @params

    $params = @{} + $PSBoundParameters
    $keyNames = @(
        'Pip3Package'
        'Force'
    )
    $removeKeyNames = $params.Keys | Where-Object { !($_ -in $keyNames) }
    $removeKeyNames | ForEach-Object { $params.Remove($_) }
    $params.Optimize = & $shouldOptimize -Provider 'Pip'
    Install-Pip3 @params
}