public/Install-OSDeploySoftware.ps1
|
#Requires -PSEdition Core #Requires -Version 7.4 function Install-OSDeploySoftware { <# .SYNOPSIS Lists, previews, downloads, or installs OSDeploy software components .DESCRIPTION Provides four modes for OSDeploy workstation software. With no Name, returns the available component list and a preview command for each item. With Name but without Force or DownloadOnly, returns metadata describing the selected components. Force invokes each selected installer. DownloadOnly downloads supported installers without installing them and takes precedence as the action when both switches are specified. Supported names are adk-25h2, adk-26h1, mdt, git, code, code-insiders, hyperv, and 7zip. DownloadOnly is reported as unsupported for Hyper-V, Git, and both Visual Studio Code channels. Hyper-V installation is also reported as unsupported inside a VM. WhatIf and Confirm apply to each requested download or installation action. After validating prerequisites, initializes OSDeployCore paths and migrates supported legacy cache content before listing, previewing, downloading, or installing components. .PARAMETER Name Specifies one or more validated component identifiers. The alias is Component. When omitted or empty, returns the complete component list instead of taking an action. .PARAMETER Force Invokes the installer for each selected component instead of returning preview metadata. .PARAMETER DownloadOnly Downloads supported ADK, MDT, or 7-Zip installers without installing them. Hyper-V and winget-based components return a NotSupported result. When combined with Force, the supported component helpers still receive DownloadOnly. .EXAMPLE PS> Install-OSDeploySoftware Returns all available component names, display names, actions, and preview commands. .EXAMPLE PS> Install-OSDeploySoftware -Name 'adk-26h1' Returns preview metadata for Windows ADK 26H1 without downloading or installing it. .EXAMPLE PS> Install-OSDeploySoftware -Name 'adk-26h1' -Force Downloads and installs Windows ADK 26H1 and its Windows PE add-on. .EXAMPLE PS> Install-OSDeploySoftware -Name 'mdt' -DownloadOnly Downloads the MDT installer without installing it. .INPUTS None. This function does not accept pipeline input. .OUTPUTS System.Management.Automation.PSCustomObject. With no Name, returns objects containing Name, FullName, Action, and Command. Preview objects contain Name, Component, Action, Source, Docs, Details, Note, and Command. Action results contain component and status data; skipped or unsupported results include explanatory properties. .NOTES Author: David Segura Company: Recast Software Version: 1.0.0 Date: 2026-08-28 Requires Windows 11 25H2 or later, PowerShell 7.4 or later installed from MSI, curl.exe, and Administrator rights. Winget-based installers also assume winget is available. MDT is retired by Microsoft. Use it only for existing workflows that still depend on it. .LINK https://learn.microsoft.com/en-us/windows-hardware/get-started/adk-install .LINK https://learn.microsoft.com/en-us/troubleshoot/mem/configmgr/mdt/mdt-retirement #> [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Medium')] param ( [Alias('Component')] [ValidateSet( 'adk-25h2', 'adk-26h1', 'mdt', 'git', 'code', 'code-insiders', 'hyperv', '7zip' )] [string[]] $Name, [switch] $Force, [switch] $DownloadOnly ) #================================================= # Stop before inspecting, downloading, or installing components when prerequisites are missing. if (-not (Test-IsWindows11)) { throw "[$(Get-Date -Format s)] [$($MyInvocation.MyCommand.Name)] Windows 11 is required." } if (-not (Test-IsWindows1125H2)) { throw "[$(Get-Date -Format s)] [$($MyInvocation.MyCommand.Name)] Windows 11 25H2 (build 26200) is required." } if (-not (Test-PwshVersionMin)) { throw "[$(Get-Date -Format s)] [$($MyInvocation.MyCommand.Name)] PowerShell 7.4 or higher is required." } if (-not (Test-PwshPSHome)) { throw "[$(Get-Date -Format s)] [$($MyInvocation.MyCommand.Name)] The MSI installation of PowerShell 7 is required." } if (-not (Test-CommandCurl)) { throw "[$(Get-Date -Format s)] [$($MyInvocation.MyCommand.Name)] curl.exe is required but was not found in the current PATH. curl.exe ships with Windows 10 1803+." } if (-not (Test-IsAdministrator)) { throw "[$(Get-Date -Format s)] [$($MyInvocation.MyCommand.Name)] Administrator rights are required. Re-run PowerShell as Administrator and try again." } Initialize-OSDeployCorePaths #================================================= # Auto-download pwsh installer every time unless already cached # TODO: re-enable when ready # try { # $pwshWinget = Get-Command -Name 'winget' -ErrorAction SilentlyContinue # if ($pwshWinget) { # $pwshSoftwarePath = Join-Path -Path $script:OSDeployCoreSoftwarePath -ChildPath 'Microsoft.PowerShell' # $pwshAlreadyCached = (Test-Path -Path $pwshSoftwarePath) -and # (Get-ChildItem -Path $pwshSoftwarePath -File -ErrorAction SilentlyContinue | Select-Object -First 1) # if (-not $pwshAlreadyCached) { # New-Item -Path $pwshSoftwarePath -ItemType Directory -Force | Out-Null # $pwshPackageId = if ($global:OSDeployModule -and $global:OSDeployModule.Software.pwsh.wingetid) { # [string]$global:OSDeployModule.Software.pwsh.wingetid # } else { 'Microsoft.PowerShell' } # Write-Host "[$(Get-Date -Format s)] [Install-OSDeploySoftware] Downloading $pwshPackageId to $pwshSoftwarePath..." -ForegroundColor DarkGray # & $pwshWinget.Source download --id $pwshPackageId --download-directory $pwshSoftwarePath --accept-source-agreements --accept-package-agreements # } # } else { # Write-Verbose "[$($MyInvocation.MyCommand.Name)] [Install-OSDeploySoftware] winget not found; skipping auto-download of pwsh." # } # } catch { # Write-Verbose "[$($MyInvocation.MyCommand.Name)] [Install-OSDeploySoftware] pwsh auto-download skipped: $_" # } $availableComponents = @( 'adk-25h2' 'adk-26h1' 'mdt' 'git' 'code' 'code-insiders' 'hyperv' '7zip' ) # Return the available components when the caller does not select a component. if (-not $PSBoundParameters.ContainsKey('Name') -or -not $Name -or $Name.Count -eq 0) { $componentFullName = @{ 'pwsh' = 'PowerShell 7' 'PowerShell 7' = 'PowerShell 7' 'adk-25h2' = 'Windows ADK 25H2' 'adk-26h1' = 'Windows ADK 26H1' 'mdt' = 'Microsoft Deployment Toolkit' 'git' = 'Git for Windows' 'code' = 'Visual Studio Code' 'code-insiders' = 'Visual Studio Code Insiders' 'hyperv' = 'Hyper-V' '7zip' = '7-Zip' } $options = foreach ($option in $availableComponents) { [pscustomobject]@{ Name = $option FullName = $componentFullName[$option] Action = 'Install' Command = "Install-OSDeploySoftware -Name '$option'" } } return $options } $componentFullName = @{ 'pwsh' = 'PowerShell 7' 'PowerShell 7' = 'PowerShell 7' 'adk-25h2' = 'Windows ADK 25H2' 'adk-26h1' = 'Windows ADK 26H1' 'mdt' = 'Microsoft Deployment Toolkit' 'git' = 'Git for Windows' 'code' = 'Visual Studio Code' 'code-insiders' = 'Visual Studio Code Insiders' 'hyperv' = 'Hyper-V' 'Hyper-V' = 'Hyper-V' '7zip' = '7-Zip' '7-Zip' = '7-Zip' } $componentMetadata = @{ 'pwsh' = @{ Component = 'PowerShell 7' Source = $global:OSDeployModule.Software.pwsh.wingetid Docs = $global:OSDeployModule.Software.pwsh.docs Details = $global:OSDeployModule.Software.pwsh.wiki } 'adk-25h2' = @{ Component = 'Windows ADK 25H2' Source = $global:OSDeployModule.Software.adk.'25h2'.adksetup Docs = $global:OSDeployModule.Software.adk.docs Details = $global:OSDeployModule.Software.adk.'25h2'.wiki } 'adk-26h1' = @{ Component = 'Windows ADK 26H1' Source = $global:OSDeployModule.Software.adk.'26h1'.adksetup Docs = $global:OSDeployModule.Software.adk.docs Details = $global:OSDeployModule.Software.adk.'26h1'.wiki } 'mdt' = @{ Component = 'Microsoft Deployment Toolkit' Source = $global:OSDeployModule.Software.mdt.msi Docs = $global:OSDeployModule.Software.mdt.docs Details = $global:OSDeployModule.Software.mdt.retirement } 'git' = @{ Component = 'Git for Windows' Source = $global:OSDeployModule.Software.git.wingetid Docs = 'https://git-scm.com/download/win' Details = 'https://winget.run/pkg/Git/Git' } 'hyperv' = @{ Component = 'Hyper-V' Source = $global:OSDeployModule.Software.hyperv.featurename Docs = $global:OSDeployModule.Software.hyperv.docs Details = $global:OSDeployModule.Software.hyperv.docs } 'code' = @{ Component = 'Visual Studio Code' Source = $global:OSDeployModule.Software.vscode.stable.wingetid Docs = $global:OSDeployModule.Software.vscode.docs Details = $global:OSDeployModule.Software.vscode.wiki } 'code-insiders' = @{ Component = 'Visual Studio Code Insiders' Source = $global:OSDeployModule.Software.vscode.insiders.wingetid Docs = $global:OSDeployModule.Software.vscode.docs Details = $global:OSDeployModule.Software.vscode.wiki } '7zip' = @{ Component = '7-Zip' Source = $global:OSDeployModule.Software.'7zip'.id Docs = $global:OSDeployModule.Software.'7zip'.releases Details = $global:OSDeployModule.BootImage.winpeapps.sevenzip.standalone } } # Return component metadata unless an install or download action was requested. if (-not $Force -and -not $DownloadOnly) { $preview = foreach ($item in $Name) { $metadata = $componentMetadata[$item] [pscustomobject]@{ Name = $item Component = $metadata.Component Action = 'Preview' Source = $metadata.Source Docs = $metadata.Docs Details = $metadata.Details Note = 'Add -Force to install or -DownloadOnly to download only.' Command = "Install-OSDeploySoftware -Name '$item' -Force" } } return $preview } $results = [System.Collections.Generic.List[pscustomobject]]::new() # Components that use Windows features or winget (which downloads to %TEMP%\WinGet) have no separate downloadable asset. $downloadOnlyUnsupported = @('hyperv', 'git', 'code', 'code-insiders') foreach ($item in $Name) { # Skip Hyper-V because nested installation is unsupported. if (Test-IsVM -and $item -eq 'hyperv') { $results.Add([pscustomobject]@{ Name = $item Component = $componentFullName[$item] Action = 'Skipped' Status = 'NotSupported' Note = 'Hyper-V cannot be installed inside a virtual machine.' }) continue } # Report unsupported download-only components without attempting an action. if ($DownloadOnly -and $item -in $downloadOnlyUnsupported) { $results.Add([pscustomobject]@{ Name = $item Component = $componentFullName[$item] Action = 'DownloadOnly' Status = 'NotSupported' Note = '-DownloadOnly is not supported for Windows feature-based components.' }) continue } $resolvedComponent = if ($componentFullName.ContainsKey($item)) { $componentFullName[$item] } else { $item } # Honor WhatIf and Confirm for every component-changing action. switch ($resolvedComponent) { 'Windows ADK 25H2' { if ($PSCmdlet.ShouldProcess('Windows ADK 25H2', ($DownloadOnly ? 'Download' : 'Install'))) { Install-SoftwareMicrosoftWindowsAdk25H2 -DownloadOnly:$DownloadOnly $results.Add([pscustomobject]@{ Component = 'Windows ADK 25H2' Status = ($DownloadOnly ? 'Downloaded' : 'Installed') }) } } 'Windows ADK 26H1' { if ($PSCmdlet.ShouldProcess('Windows ADK 26H1', ($DownloadOnly ? 'Download' : 'Install'))) { Install-SoftwareMicrosoftWindowsAdk26H1 -DownloadOnly:$DownloadOnly $results.Add([pscustomobject]@{ Component = 'Windows ADK 26H1' Status = ($DownloadOnly ? 'Downloaded' : 'Installed') }) } } 'Microsoft Deployment Toolkit' { if ($PSCmdlet.ShouldProcess('Microsoft Deployment Toolkit', ($DownloadOnly ? 'Download' : 'Install'))) { Install-SoftwareMicrosoftDeploymentToolkit -DownloadOnly:$DownloadOnly $results.Add([pscustomobject]@{ Component = 'Microsoft Deployment Toolkit' Status = ($DownloadOnly ? 'Downloaded' : 'Installed') }) } } 'PowerShell 7' { if ($PSCmdlet.ShouldProcess('PowerShell 7', 'Install')) { Install-SoftwareMicrosoftPwsh $results.Add([pscustomobject]@{ Component = 'PowerShell 7' Status = 'Installed' }) } } 'Visual Studio Code' { if ($PSCmdlet.ShouldProcess('Visual Studio Code', 'Install')) { Install-SoftwareMicrosoftVSCode $results.Add([pscustomobject]@{ Component = 'Visual Studio Code' Status = 'Installed' }) } } 'Visual Studio Code Insiders' { if ($PSCmdlet.ShouldProcess('Visual Studio Code Insiders', 'Install')) { Install-SoftwareMicrosoftVSCodeInsiders $results.Add([pscustomobject]@{ Component = 'Visual Studio Code Insiders' Status = 'Installed' }) } } 'Git for Windows' { if ($PSCmdlet.ShouldProcess('Git for Windows', 'Install')) { Install-SoftwareGitForWindows $results.Add([pscustomobject]@{ Component = 'Git for Windows' Status = 'Installed' }) } } 'Hyper-V' { if ($PSCmdlet.ShouldProcess('Hyper-V', 'Install')) { $hvResult = Install-SoftwareMicrosoftHyperV $results.Add([pscustomobject]@{ Component = 'Hyper-V' Status = 'Installed' RestartNeeded = ($hvResult.RestartNeeded -eq $true) }) } } '7-Zip' { if ($PSCmdlet.ShouldProcess('7-Zip', ($DownloadOnly ? 'Download' : 'Install'))) { Install-Software7Zip -DownloadOnly:$DownloadOnly $results.Add([pscustomobject]@{ Component = '7-Zip' Status = ($DownloadOnly ? 'Downloaded' : 'Installed') }) } } } } $results } |