# Check PowerShell version for use in path if ($PSVersionTable.PSVersion.Major -ge 3) { $currentDir = $PSScriptRoot } else { $currentDir = (Get-Item .).FullName } # Import script-helper.ps1 . "$currentDir\script-helper.ps1" # Check if winget is available $wingetAvailable = $true if (-not (Get-Command winget -ErrorAction SilentlyContinue)) { Write-Host "winget is not installed. Winget-based installs will be skipped." -ForegroundColor Yellow $wingetAvailable = $false } # Get system information $systemInfo = Get-SystemInfo # Path to the UWP apps list file $uwpFolder = "$windowsDrive\Windows\OEM\apps\uwp" $uwpAppsFile = "$windowsDrive\Windows\OEM\apps\uwp-apps.json" # Path to the winget apps list file $wingetAppsFile = "$windowsDrive\Windows\OEM\apps\winget.json" # Determine system architecture (normalize to x64/x86/arm64) $systemArch = $systemInfo.Architecture.ToLower() switch ($systemArch) { "64-bit" { $systemArch = "x64" } "32-bit" { $systemArch = "x86" } } #region Helper Functions # Create a temporary download cache $downloadCache = "$env:TEMP\uwp-cache" New-Item -ItemType Directory -Path $downloadCache -Force | Out-Null # Helper to download remote package if not already cached function Get-RemotePackage { param( [string]$Url ) $fileName = Split-Path $Url -Leaf $dest = Join-Path $downloadCache $fileName if (-not (Test-Path $dest)) { Write-Host "Downloading $fileName..." -ForegroundColor Cyan try { Invoke-WebRequest -Uri $Url -OutFile $dest -UseBasicParsing -TimeoutSec 15 } catch { Write-Host "Download failed or timed out for $Url : $($_.Exception.Message)" -ForegroundColor Red return $null } } return $dest } # Function to check if a package is already installed function Test-PackageInstalled { param([string]$packageName) try { $package = Get-AppxPackage -Name $packageName -ErrorAction SilentlyContinue return ($null -ne $package) } catch { return $false } } # Function to extract package name from filename function Get-PackageNameFromFile { param([string]$filename) # Remove extension and architecture info to get base package name $baseName = [System.IO.Path]::GetFileNameWithoutExtension($filename) # Extract package family name (everything before version) if ($baseName -match '^([^_]+(?:_[^_]+)*)_\d') { return $matches[1] } return $baseName } # Function to check if a newer version is already installed function Test-NewerVersionInstalled { param( [string]$packageName, [string]$fileVersion ) try { $installedPackage = Get-AppxPackage -Name $packageName -ErrorAction SilentlyContinue if (-not $installedPackage) { return $false } # Compare versions - if installed version is greater than file version, skip $installedVersion = $installedPackage.Version if ([System.Version]$installedVersion -gt [System.Version]$fileVersion) { return $true } return $false } catch { return $false } } # Function to extract version from filename function Get-VersionFromFile { param([string]$filename) $baseName = [System.IO.Path]::GetFileNameWithoutExtension($filename) # Pattern: name_version_arch if ($baseName -match '_(\d+\.\d+\.\d+\.\d+)_') { return $matches[1] } elseif ($baseName -match '_(\d+\.\d+\.\d+\.\d+)\.') { return $matches[1] } return "0.0.0.0" } function Install-FromStore { param( [string]$ProductId ) Write-Host "Installing from Microsoft Store ($ProductId)..." -ForegroundColor Cyan $result = winget install ` --id $ProductId ` --source msstore ` --accept-package-agreements ` --accept-source-agreements ` --silent return ($LASTEXITCODE -eq 0) } function Install-WingetApp { param( [Parameter(Mandatory)] [string[]]$Arguments ) if (-not $wingetAvailable) { return } Write-Host "winget $($Arguments -join ' ')" -ForegroundColor DarkGray winget @Arguments } # Function to resolve dependency file path from the new JSON structure function Get-DependencyFilePath { param( [string]$dependencyName, [string]$version, [string]$arch, [object]$dependenciesTable ) if (-not $dependenciesTable.$dependencyName) { Write-Host "Dependency $dependencyName not found in dependencies table" -ForegroundColor Red return $null } if (-not $dependenciesTable.$dependencyName.$version) { Write-Host "Version $version of $dependencyName not found in dependencies table" -ForegroundColor Red return $null } $archPath = $dependenciesTable.$dependencyName.$version.$arch if (-not $archPath) { Write-Host "Architecture $arch not available for $dependencyName version $version" -ForegroundColor Yellow return $null } return Join-Path $uwpFolder $archPath } #endregion #region UWP App Installation if (-not (Test-Path $uwpAppsFile)) { Write-Host "No UWP apps list file found at $uwpAppsFile. Skipping UWP installation." -ForegroundColor Yellow } else { Write-Host "`n========================================" -ForegroundColor Magenta Write-Host "Installing UWP Apps" -ForegroundColor Magenta Write-Host "========================================`n" -ForegroundColor Magenta # Load UWP apps JSON $uwpAppsData = Get-Content $uwpAppsFile | ConvertFrom-Json $uwpApps = $uwpAppsData.apps $dependenciesTable = $uwpAppsData.dependencies # Install dependencies and main apps per app foreach ($app in $uwpApps) { $appPackageFamilyName = $app.packageFamilyName Write-Host "Installing $appPackageFamilyName..." -ForegroundColor Green # Collect valid dependencies for this app $validDependencies = @() # Install dependencies per app (new format) if ($app.dependencies) { foreach ($depName in $app.dependencies.PSObject.Properties.Name) { $requiredVersion = $app.dependencies.$depName.version Write-Host "Processing dependency: $depName (version $requiredVersion)..." -ForegroundColor Cyan # Get the dependency file path for current architecture $depPath = Get-DependencyFilePath -dependencyName $depName ` -version $requiredVersion ` -arch $systemArch ` -dependenciesTable $dependenciesTable if (-not $depPath) { Write-Host "Could not resolve dependency path for $depName" -ForegroundColor Red continue } if (-not (Test-Path $depPath)) { Write-Host "Dependency file not found: $depPath" -ForegroundColor Red continue } # Extract package name and version from file $depFileName = Split-Path $depPath -Leaf $packageName = Get-PackageNameFromFile $depFileName $fileVersion = Get-VersionFromFile $depFileName # Check if package is already installed $installedPackage = Get-AppxPackage -Name $packageName -ErrorAction SilentlyContinue if ($installedPackage) { if (Test-NewerVersionInstalled -packageName $packageName -fileVersion $fileVersion) { Write-Host "Newer version of $packageName already installed ($($installedPackage.Version) > $fileVersion), skipping..." -ForegroundColor Green } else { Write-Host "Dependency $packageName is already installed, skipping..." -ForegroundColor Green } $validDependencies += $depPath } else { Write-Host "Installing dependency $depFileName..." -ForegroundColor Cyan try { Add-AppxPackage -Path $depPath -ForceApplicationShutdown -ForceUpdateFromAnyVersion -ErrorAction Stop Write-Host "Successfully installed dependency: $depFileName" -ForegroundColor Green $validDependencies += $depPath } catch { Write-Host "Failed to install dependency $depFileName : $($_.Exception.Message)" -ForegroundColor Red # Continue with installation even if dependency fails } } } } # Select main package file for this system architecture $mainAppPath = $null switch ($app.installSource) { "Store" { if ($app.store.productId) { if (Install-FromStore $app.store.productId) { Write-Host "Installed via Microsoft Store." -ForegroundColor Green continue } else { Write-Host "Store install failed." -ForegroundColor Red continue } } } "Url" { $selectedFile = $app.urlFiles | Where-Object { $_.arch -contains $systemArch } | Select-Object -First 1 if (-not $selectedFile) { $selectedFile = $app.urlFiles | Where-Object { $_.arch -contains "neutral" } | Select-Object -First 1 } if (-not $selectedFile) { Write-Host "No compatible URL file found." -ForegroundColor Red continue } $mainAppPath = Get-RemotePackage $selectedFile.url if (-not $mainAppPath) { continue } } default { # Local (new format uses 'files' array) $selectedFile = $app.files | Where-Object { $_.arch -contains $systemArch } | Select-Object -First 1 if (-not $selectedFile) { $selectedFile = $app.files | Where-Object { $_.arch -contains "neutral" } | Select-Object -First 1 } if (-not $selectedFile) { Write-Host "No compatible local file found for $appPackageFamilyName (System Arch: $systemArch)" -ForegroundColor Red continue } $mainAppPath = Join-Path $uwpFolder $selectedFile.path } } # Check if main app file exists if (-not (Test-Path $mainAppPath)) { Write-Host "Main app file not found: $mainAppPath" -ForegroundColor Red continue } # Check if a newer version of the main app is already installed $mainPackageName = Get-PackageNameFromFile $selectedFile.path $mainFileVersion = Get-VersionFromFile $selectedFile.path $installedMainApp = Get-AppxPackage -Name $mainPackageName -ErrorAction SilentlyContinue if ($installedMainApp) { if (Test-NewerVersionInstalled -packageName $mainPackageName -fileVersion $mainFileVersion) { Write-Host "Newer version of $mainPackageName already installed ($($installedMainApp.Version) > $mainFileVersion), skipping..." -ForegroundColor Green continue } else { Write-Host "App $mainPackageName is already installed, attempting update..." -ForegroundColor Yellow } } $params = @{ Path = $mainAppPath ForceApplicationShutdown = $true ForceUpdateFromAnyVersion = $true } if ($app.licensePath) { $licensePath = Join-Path $uwpFolder $app.licensePath if (Test-Path $licensePath) { $params.LicensePath = $licensePath } else { Write-Host "License file not found: $licensePath" -ForegroundColor Yellow } } if ($app.externalLocation) { $externalLocation = Join-Path $uwpFolder $app.externalLocation if (Test-Path $externalLocation) { $params.ExternalLocation = $externalLocation } else { Write-Host "External location not found: $externalLocation" -ForegroundColor Yellow } } # Only add DependencyPath if we have valid dependencies that were successfully installed if ($validDependencies.Count -gt 0) { $params.DependencyPath = $validDependencies } # Apply optional flags (using camelCase from new JSON format) foreach ($flag in @( "requiredContentGroupOnly", "forceTargetApplicationShutdown", "installAllResources", "preventRegistration", "register", "stageOnly", "deferRegistrationWhenPackagesAreInUse" )) { if ($app.$flag -eq $true) { # Convert camelCase to PascalCase for PowerShell parameter $paramName = $flag.Substring(0,1).ToUpper() + $flag.Substring(1) $params[$paramName] = $true } } # Install the app with error handling try { Write-Host "Installing main app: $appPackageFamilyName..." -ForegroundColor Cyan Add-AppxPackage @params -ErrorAction Stop Write-Host "Successfully installed: $appPackageFamilyName" -ForegroundColor Green } catch { Write-Host "Failed to install $appPackageFamilyName : $($_.Exception.Message)" -ForegroundColor Red # Try installing without dependencies if that was the issue if ($_.Exception.Message -like "*was provided but not used*") { Write-Host "Retrying without dependency path..." -ForegroundColor Yellow try { $params.Remove('DependencyPath') Add-AppxPackage @params -ErrorAction Stop Write-Host "Successfully installed $appPackageFamilyName without dependencies" -ForegroundColor Green } catch { Write-Host "Failed to install even without dependencies: $($_.Exception.Message)" -ForegroundColor Red } } } } Write-Host "UWP app installation completed!" -ForegroundColor Green } #endregion #region Winget App Installation if (-not $wingetAvailable) { Write-Host "`nwinget is not available. Skipping winget app installation." -ForegroundColor Yellow } elseif (-not (Test-Path $wingetAppsFile)) { Write-Host "`nNo winget apps list file found at $wingetAppsFile. Skipping winget app installation." -ForegroundColor Yellow } else { Write-Host "`n========================================" -ForegroundColor Magenta Write-Host "Installing Winget Apps" -ForegroundColor Magenta Write-Host "========================================`n" -ForegroundColor Magenta $wingetApps = Get-Content $wingetAppsFile | ConvertFrom-Json foreach ($app in $wingetApps) { $appId = $app.Id Write-Host "Processing $appId..." -ForegroundColor Cyan # Select the correct variant by architecture (fallback to neutral) $variant = $app.Variants | Where-Object { $_.Arch -contains $systemArch } | Select-Object -First 1 if (-not $variant) { $variant = $app.Variants | Where-Object { $_.Arch -contains "neutral" } | Select-Object -First 1 } if (-not $variant) { Write-Host "No matching architecture variant found for $appId (System Arch: $systemArch)" -ForegroundColor Red continue } Write-Host "Installing $appId for architecture $systemArch..." -ForegroundColor Green # Base command $wingetArgs = @( "install", "--id", $appId, "--accept-source-agreements", "--accept-package-agreements", "--silent" ) # Architecture from chosen variant if ($variant.Arch) { $wingetArgs += "--architecture" $wingetArgs += $systemArch } # Variant version if included if ($variant.Version) { $wingetArgs += "--version" $wingetArgs += $variant.Version } # Global (non-arch) properties if ($app.Source) { $wingetArgs += @("--source", $app.Source) } if ($app.Scope) { $wingetArgs += @("--scope", $app.Scope) } if ($app.Mode) { $wingetArgs += @("--mode", $app.Mode) } if ($app.InstallationPath) { $wingetArgs += @("--location", $app.InstallationPath) } if ($app.Log) { $wingetArgs += @("--log", $app.Log) } if ($app.Override) { $wingetArgs += @("--override", $app.Override) } # Boolean flags foreach ($flag in @("Force","Interactive","DisableInteractivity","VerboseLogs","NoUpgrade","Help","Wait","OpenLogs")) { if ($app.$flag -eq $true) { $wingetArgs += "--" + ($flag -replace '([A-Z])', '-$1').ToLower() } } # Execute installation winget @wingetArgs } Write-Host "`nWinget app installation completed!" -ForegroundColor Green } #endregion Write-Host "`n========================================" -ForegroundColor Magenta Write-Host "All app installations completed!" -ForegroundColor Magenta Write-Host "========================================" -ForegroundColor Magenta