functions/Install-App.ps1
function Install-WUApp { [CmdletBinding()] param ( [string[]] $ScoopApp, [hashtable] $ScoopBucket, [string[]] $ChocolateyPackage, [string[]] $PSModule, [string[]] $PipPackage, [string[]] $NpmPackage, [string[]] $NuGetPackage, [string] $Destination, # Only available for `NuGetPackage` installation [string] $RequiredVersion, [switch] $Unsafe, [switch] $Force, [ValidateSet('All', 'Scoop', 'Chocolatey', 'PSModule', 'pip', 'npm', 'NuGet')] [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 Optimize-PackageManagement { [CmdletBinding(SupportsShouldProcess)] param ( ) # Install package provider 'NuGet'. if (!(Get-PackageProvider | Where-Object Name -EQ 'NuGet')) { Install-PackageProvider -Name 'NuGet' -Force -Scope CurrentUser } # Update PowerShellGet Module if (@(Get-Module 'PowerShellGet' -ListAvailable).Count -eq 1) { Install-Module -Name PowerShellGet -Force -AllowClobber -Scope CurrentUser -WarningAction Ignore Update-Module -Name PowerShellGet } # Set 'PSGallery' and 'NuGet' to trusted Get-PackageSource | ` Where-Object ProviderName -Match 'PowerShellGet|NuGet' | ` Where-Object IsTrusted -EQ $false | ` ForEach-Object { $_ | Set-PackageSource -Trusted } | Out-String | Write-Verbose # for issue: https://github.com/PowerShell/PowerShellGetv2/issues/606 "$env:LOCALAPPDATA\Microsoft\Windows\PowerShell\PowerShellGet\NuGet.exe" | Where-Object { !(Test-Path -LiteralPath $_ -PathType Leaf) } | ForEach-Object { (New-Object System.Net.WebClient).DownloadFile('https://dist.nuget.org/win-x86-commandline/latest/nuget.exe', $_) } } function Install-PowerShellModule { [CmdletBinding(SupportsShouldProcess)] param ( [string[]] $PSModule, [switch] $Force, [switch] $Optimize ) if ($Optimize -or $PSModule) { Optimize-PackageManagement $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-Pip { [CmdletBinding(SupportsShouldProcess)] param ( [string[]] $PipPackage, [switch] $Force, [switch] $Optimize ) if ($Optimize -or $PipPackage) { if (!(Get-Command -Name 'pip' -ErrorAction Ignore)) { Install-Scoop -ScoopApp 'python' } $PipPackage | Where-Object { $_ } | ForEach-Object { if ($Force) { pip install --upgrade --force-reinstall $_ } else { pip install --upgrade $_ } } } } function Install-Npm { [CmdletBinding(SupportsShouldProcess)] param ( [string[]] $NpmPackage, [switch] $Force, [switch] $Optimize ) if ($Optimize -or $NpmPackage) { if (!(Get-Command -Name 'npm' -ErrorAction Ignore)) { Install-Scoop -ScoopApp 'nodejs' } $NpmPackage | Where-Object { $_ } | ForEach-Object { if ($Force) { npm install --global --force $_ } else { npm install --global $_ } } } } function Install-NuGet { [CmdletBinding(SupportsShouldProcess)] param ( [string[]] $NuGetPackage, [string] [ValidateNotNullOrEmpty()] $Destination = ( $env:NUGET_PACKAGE_DIR, $PWD | Where-Object { $_ } | Where-Object { Test-Path -LiteralPath $_ -PathType Container } | Select-Object -First 1 ), [string] $RequiredVersion, [switch] $Optimize ) if ($Optimize -or $NuGetPackage) { Optimize-PackageManagement if (!(Get-Command -Name nuget -ErrorAction Ignore)) { Install-Scoop -ScoopApp 'nuget' } if (!(Test-WUPathProperty -LiteralPath $Destination -PSProvider FileSystem -PathType Container)) { New-Item -Path $Destination -ItemType 'Directory' -Force | Out-String | Write-Verbose if (!(Test-WUPathProperty -LiteralPath $Destination -PSProvider FileSystem -PathType Container)) { return } } # Install-Package -Name $_ -Destination $Destination -ProviderName NuGet @installPackageArgs # The above code is slow. $NuGetPackage | Where-Object { $_ } | ForEach-Object { $cmd = 'nuget install "{0}"' -f $_ $cmd = '{0} -OutputDirectory "{1}"' -f $cmd, ($Destination | Convert-WUString -Type EscapeForPowerShellDoubleQuotation) if ($RequiredVersion) { $cmd = '{0} -Version "{1}"' -f $cmd, $RequiredVersion } Invoke-Expression $cmd } } } $params = @{} + $PSBoundParameters $keyNames = @( 'PSModule' 'Force' ) [string[]]$params.Keys | Where-Object { !($_ -in $keyNames) } | ForEach-Object { $params.Remove($_) } $params.Optimize = & $shouldOptimize -Provider 'PSModule' Install-PowerShellModule @params | ForEach-Object { Write-Host $_ } $params = @{} + $PSBoundParameters $keyNames = @( 'ScoopApp' 'ScoopBucket' 'Unsafe' 'Force' ) [string[]]$params.Keys | Where-Object { !($_ -in $keyNames) } | ForEach-Object { $params.Remove($_) } $params.Optimize = & $shouldOptimize -Provider 'Scoop' Install-Scoop @params | ForEach-Object { Write-Host $_ } $params = @{} + $PSBoundParameters $keyNames = @( 'ChocolateyPackage' 'Unsafe' 'Force' ) [string[]]$params.Keys | Where-Object { !($_ -in $keyNames) } | ForEach-Object { $params.Remove($_) } $params.Optimize = & $shouldOptimize -Provider 'Chocolatey' Install-Chocolatey @params | ForEach-Object { Write-Host $_ } $params = @{} + $PSBoundParameters $keyNames = @( 'PipPackage' 'Force' ) [string[]]$params.Keys | Where-Object { !($_ -in $keyNames) } | ForEach-Object { $params.Remove($_) } $params.Optimize = & $shouldOptimize -Provider 'pip' Install-Pip @params | ForEach-Object { Write-Host $_ } $params = @{} + $PSBoundParameters $keyNames = @( 'NpmPackage' 'Force' ) [string[]]$params.Keys | Where-Object { !($_ -in $keyNames) } | ForEach-Object { $params.Remove($_) } $params.Optimize = & $shouldOptimize -Provider 'npm' Install-Npm @params | ForEach-Object { Write-Host $_ } $params = @{} + $PSBoundParameters $keyNames = @( 'NuGetPackage' 'Destination' 'RequiredVersion' ) [string[]]$params.Keys | Where-Object { !($_ -in $keyNames) } | ForEach-Object { $params.Remove($_) } $params.Optimize = & $shouldOptimize -Provider 'NuGet' Install-NuGet @params | ForEach-Object { Write-Host $_ } } |