private/Software/Install-7Zip.ps1
|
#Requires -PSEdition Core <# .SYNOPSIS Downloads, installs, and caches 7-Zip on the host and prepares the WinPE cache. .DESCRIPTION Ensures 7-Zip is available on the host by: 1. Downloading the 7-Zip installer for both amd64 and arm64 architectures to C:\ProgramData\OSDeployCore\Software\7zip.7zip\ using winget download. 2. Installing 7-Zip on the host via winget when it is not already present (skipped when -DownloadOnly is specified). 3. Pre-populating the WinPE apps cache with 7zr.exe and the extracted 7z-extra archive so that subsequent boot image builds find the files already cached. This mirrors the logic in Step-BootImageAppZip. .PARAMETER DownloadOnly Downloads the installer and WinPE cache files without installing 7-Zip on the host. .OUTPUTS System.Management.Automation.PSCustomObject .EXAMPLE Install-7Zip Installs 7-Zip if not present and populates the WinPE cache. .EXAMPLE Install-7Zip -DownloadOnly Downloads the 7-Zip installers and WinPE cache files without installing. .NOTES Author: David Segura Company: Recast Software This function is supported only on Windows and requires winget. #> function Install-7Zip { [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Medium')] [OutputType([pscustomobject])] param ( [Parameter()] [switch] $DownloadOnly ) if (-not $IsWindows) { throw "[$(Get-Date -format s)] [$($MyInvocation.MyCommand.Name)] Install-7Zip is supported only on Windows." } $winget = Get-Command -Name 'winget' -ErrorAction SilentlyContinue if (-not $winget) { throw "[$(Get-Date -format s)] [$($MyInvocation.MyCommand.Name)] winget is required but was not found. Install App Installer from Microsoft Store and try again." } $packageId = '7zip.7zip' if ($global:OSDeployModule -and $global:OSDeployModule.Software.'7zip'.id) { $packageId = [string]$global:OSDeployModule.Software.'7zip'.id } #region Download installer for both architectures $softwarePath = Join-Path $script:OSDeployCoreSoftwarePath $packageId $amd64Path = Join-Path $softwarePath 'amd64' $arm64Path = Join-Path $softwarePath 'arm64' foreach ($archEntry in @( @{ Path = $amd64Path; Arch = 'x64'; Label = 'amd64' } @{ Path = $arm64Path; Arch = 'arm64'; Label = 'arm64' } )) { $archPath = $archEntry.Path $archArg = $archEntry.Arch $archLabel = $archEntry.Label $alreadyCached = (Test-Path -Path $archPath) -and (Get-ChildItem -Path $archPath -File -ErrorAction SilentlyContinue | Select-Object -First 1) if ($alreadyCached) { Write-Host "[$(Get-Date -format s)] [$($MyInvocation.MyCommand.Name)] 7-Zip installer already cached ($archLabel): $archPath" -ForegroundColor Green } else { if (-not (Test-Path -Path $archPath)) { New-Item -Path $archPath -ItemType Directory -Force | Out-Null } Write-Host "[$(Get-Date -format s)] [$($MyInvocation.MyCommand.Name)] Downloading 7-Zip installer ($archLabel) to $archPath..." -ForegroundColor DarkGray & $winget.Source download --id $packageId --exact --download-directory $archPath --architecture $archArg --accept-source-agreements --accept-package-agreements if ($LASTEXITCODE -ne 0) { Write-Warning "[$(Get-Date -format s)] [$($MyInvocation.MyCommand.Name)] winget download exited with code $LASTEXITCODE for architecture $archLabel." } } } #endregion #region Install 7-Zip on host (skip when -DownloadOnly) $wasInstalled = $false $skippedInstall = $false if ($DownloadOnly) { Write-Host "[$(Get-Date -format s)] [$($MyInvocation.MyCommand.Name)] -DownloadOnly specified — skipping host installation." -ForegroundColor DarkGray $skippedInstall = $true } else { $sevenZipExe = Join-Path $env:ProgramFiles '7-Zip\7z.exe' if (Test-Path -Path $sevenZipExe) { $version = (& $sevenZipExe i 2>&1 | Select-String -Pattern '^\d' | Select-Object -First 1).ToString().Trim() Write-Host "[$(Get-Date -format s)] [$($MyInvocation.MyCommand.Name)] 7-Zip is already installed: $version" -ForegroundColor Green } else { if ($PSCmdlet.ShouldProcess($packageId, 'Install 7-Zip using winget')) { Write-Host "[$(Get-Date -format s)] [$($MyInvocation.MyCommand.Name)] 7-Zip is not installed. Installing with winget..." -ForegroundColor DarkGray & $winget.Source install --id $packageId --exact -e -h --accept-source-agreements --accept-package-agreements if ($LASTEXITCODE -ne 0) { throw "[$(Get-Date -format s)] [$($MyInvocation.MyCommand.Name)] 7-Zip installation failed with exit code $LASTEXITCODE." } $wasInstalled = $true if (-not (Test-Path -Path $sevenZipExe)) { Write-Warning "[$(Get-Date -format s)] [$($MyInvocation.MyCommand.Name)] 7-Zip was installed but 7z.exe was not found at '$sevenZipExe'." } } } } #endregion #region Populate WinPE apps cache (7zr.exe + 7z-extra archive) # Mirrors Step-BootImageAppZip so the boot image build finds the files already cached. if ($global:OSDeployModule -and $global:OSDeployModule.BootImage.winpeapps.sevenzip) { $zipConfig = $global:OSDeployModule.BootImage.winpeapps.sevenzip $zipVersion = $zipConfig.version $CacheZipRoot = Join-Path $Script:OSDeployCorePath 'cache' 'winpe-apps' '7zip' $CacheZip = Join-Path $CacheZipRoot $zipVersion # Cleanup old version directories if (Test-Path -Path $CacheZipRoot) { Get-ChildItem -Path $CacheZipRoot -File -ErrorAction SilentlyContinue | Remove-Item -Force Get-ChildItem -Path $CacheZipRoot -Directory -ErrorAction SilentlyContinue | Where-Object { $_.Name -ne $zipVersion } | Remove-Item -Recurse -Force } if (Test-Path -Path $CacheZip) { Write-Host "[$(Get-Date -format s)] [$($MyInvocation.MyCommand.Name)] Using 7-Zip WinPE cache: $CacheZip" -ForegroundColor Green } else { Write-Host "[$(Get-Date -format s)] [$($MyInvocation.MyCommand.Name)] Creating 7-Zip WinPE cache: $CacheZip" -ForegroundColor DarkGray New-Item -Path $CacheZip -ItemType Directory -Force | Out-Null } # Download 7zr.exe (standalone, used to extract the extra archive) $sevenZrPath = Join-Path $CacheZip '7zr.exe' if (-not (Test-Path -Path $sevenZrPath)) { $downloadUrl = [string]$zipConfig.standalone Write-Host "[$(Get-Date -format s)] [$($MyInvocation.MyCommand.Name)] Downloading 7zr.exe from $downloadUrl" -ForegroundColor DarkGray if (Get-Command 'curl.exe' -ErrorAction SilentlyContinue) { & curl.exe --silent --location --output $sevenZrPath $downloadUrl } else { Invoke-WebRequest -UseBasicParsing -Uri $downloadUrl -OutFile $sevenZrPath } } else { Write-Host "[$(Get-Date -format s)] [$($MyInvocation.MyCommand.Name)] 7zr.exe already cached: $sevenZrPath" -ForegroundColor Green } # Download and extract the 7z-extra archive $extraDir = Join-Path $CacheZip '7za' if (-not (Test-Path -Path $extraDir)) { $downloadUrl = [string]$zipConfig.extra $extraFileName = Split-Path $downloadUrl -Leaf $downloadPath = Join-Path $CacheZip $extraFileName Write-Host "[$(Get-Date -format s)] [$($MyInvocation.MyCommand.Name)] Downloading 7-Zip extra archive: $extraFileName" -ForegroundColor DarkGray if (Get-Command 'curl.exe' -ErrorAction SilentlyContinue) { & curl.exe --silent --location --output $downloadPath $downloadUrl } else { Invoke-WebRequest -UseBasicParsing -Uri $downloadUrl -OutFile $downloadPath } Write-Host "[$(Get-Date -format s)] [$($MyInvocation.MyCommand.Name)] Extracting $extraFileName to $extraDir" -ForegroundColor DarkGray $null = & $sevenZrPath x $downloadPath -o"$extraDir" -y } else { Write-Host "[$(Get-Date -format s)] [$($MyInvocation.MyCommand.Name)] 7-Zip extra archive already extracted: $extraDir" -ForegroundColor Green } } else { Write-Verbose "[$(Get-Date -format s)] [$($MyInvocation.MyCommand.Name)] OSDeployModule or BootImage.winpeapps.sevenzip not available — skipping WinPE cache preparation." } #endregion [pscustomobject]@{ Component = '7-Zip' PackageId = $packageId WasInstalled = $wasInstalled SkippedInstall = $skippedInstall DownloadOnly = $DownloadOnly.IsPresent SoftwarePath = $softwarePath } } |