<# .SYNOPSIS Installs Microsoft Windows App for all users (RMM / SYSTEM-safe). .DESCRIPTION Downloads the official standalone MSIX and provisions it machine-wide with Add-AppxProvisionedPackage so every user gets the app. Designed to run as SYSTEM from an RMM tool. .PARAMETER Force Re-provision even if the package is already present. .PARAMETER Insider Install the Insider build instead of the public release. .EXAMPLE .\Install-WindowsApp.ps1 .EXAMPLE .\Install-WindowsApp.ps1 -Force #> [CmdletBinding()] param( [switch]$Force, [switch]$Insider ) $ErrorActionPreference = 'Stop' $ProgressPreference = 'SilentlyContinue' # Package family name used by Windows App $PackageFamilyName = 'MicrosoftCorporationII.Windows365_8wekyb3d8bbwe' $PackageNameFilter = 'MicrosoftCorporationII.Windows365' # --------------------------------------------------------------------------- # Environment checks # --------------------------------------------------------------------------- function Test-IsWindows { if ($PSVersionTable.PSVersion.Major -ge 6) { return [bool]$IsWindows } return $env:OS -eq 'Windows_NT' } function Test-IsSystem { $sid = [Security.Principal.WindowsIdentity]::GetCurrent().User.Value return ($sid -eq 'S-1-5-18') } function Get-NativeArchitecture { try { switch ([System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture) { 'X64' { return 'x64' } 'X86' { return 'x86' } 'Arm64' { return 'arm64' } } } catch {} if ($env:PROCESSOR_ARCHITECTURE -eq 'ARM64') { return 'arm64' } if ([Environment]::Is64BitOperatingSystem) { return 'x64' } return 'x86' } if (-not (Test-IsWindows)) { Write-Error "This script must run on Windows." exit 1 } $isSystem = Test-IsSystem $arch = Get-NativeArchitecture Write-Output "Running as: $([Security.Principal.WindowsIdentity]::GetCurrent().Name)" Write-Output "SYSTEM context: $isSystem" Write-Output "Architecture: $arch" # --------------------------------------------------------------------------- # Already installed? # --------------------------------------------------------------------------- function Test-WindowsAppPresent { # Provisioned (machine-wide) packages $provisioned = Get-AppxProvisionedPackage -Online -ErrorAction SilentlyContinue | Where-Object { $_.DisplayName -like "$PackageNameFilter*" -or $_.PackageName -like "$PackageNameFilter*" } if ($provisioned) { return $true } # Per-user packages (any user on the machine) $perUser = Get-AppxPackage -AllUsers -Name $PackageNameFilter -ErrorAction SilentlyContinue return [bool]$perUser } if ((Test-WindowsAppPresent) -and -not $Force) { Write-Output "Windows App is already installed/provisioned. Use -Force to reinstall." exit 0 } # --------------------------------------------------------------------------- # Download # --------------------------------------------------------------------------- function Get-DownloadUrl { param([string]$Arch, [switch]$Insider) # Official links: https://learn.microsoft.com/en-us/windows-app/whats-new?tabs=windows if ($Insider) { switch ($Arch) { 'x64' { return 'https://go.microsoft.com/fwlink/?linkid=2318620' } 'x86' { return 'https://go.microsoft.com/fwlink/?linkid=2318514' } 'arm64' { return 'https://go.microsoft.com/fwlink/?linkid=2318621' } } } else { switch ($Arch) { 'x64' { return 'https://go.microsoft.com/fwlink/?linkid=2262633' } 'x86' { return 'https://go.microsoft.com/fwlink/?linkid=2262634' } 'arm64' { return 'https://go.microsoft.com/fwlink/?linkid=2262635' } } } } $url = Get-DownloadUrl -Arch $arch -Insider:$Insider if (-not $url) { Write-Error "No download URL for architecture '$arch'." exit 1 } # Use a path SYSTEM can always write to $tempRoot = Join-Path $env:SystemRoot 'Temp\WindowsAppInstall' New-Item -ItemType Directory -Path $tempRoot -Force | Out-Null $msixPath = Join-Path $tempRoot "WindowsApp_$arch.msix" $channel = if ($Insider) { 'Insider' } else { 'Public' } Write-Output "Downloading Windows App ($channel, $arch)..." Write-Output "URL: $url" try { Invoke-WebRequest -Uri $url -OutFile $msixPath -UseBasicParsing $file = Get-Item -LiteralPath $msixPath if ($file.Length -lt 1MB) { throw "Downloaded file is too small ($($file.Length) bytes). Download may have failed." } Write-Output "Downloaded: $($file.FullName) ($([math]::Round($file.Length / 1MB, 1)) MB)" } catch { Write-Error "Download failed: $_" exit 1 } # --------------------------------------------------------------------------- # Install (provision for all users) # --------------------------------------------------------------------------- try { Write-Output "Provisioning Windows App for all users..." # Machine-wide provision — correct approach under SYSTEM / RMM # -SkipLicense is required for Store-signed packages when no license XML is supplied $result = Add-AppxProvisionedPackage -Online ` -PackagePath $msixPath ` -SkipLicense ` -ErrorAction Stop Write-Output "Add-AppxProvisionedPackage completed." if ($result) { Write-Output ($result | Format-List | Out-String) } # Also register into any currently logged-on interactive user sessions. # Provisioning alone stages the package; logged-on users get it at next logon # unless we register it now. $sessions = quser 2>$null $loggedOnUsers = @() if ($sessions) { # Best-effort: register for the current profile contexts we can reach $allUserPackages = Get-AppxPackage -AllUsers -Name $PackageNameFilter -ErrorAction SilentlyContinue if (-not $allUserPackages) { Write-Output "Attempting per-user registration for existing profiles..." try { Add-AppxPackage -Path $msixPath -ErrorAction SilentlyContinue } catch { # Expected to be limited under pure SYSTEM; provisioning is what matters Write-Output "Per-user Add-AppxPackage skipped/failed (normal under SYSTEM): $_" } } } } catch { Write-Error "Provisioning failed: $_" exit 1 } finally { # Cleanup if (Test-Path -LiteralPath $tempRoot) { Remove-Item -LiteralPath $tempRoot -Recurse -Force -ErrorAction SilentlyContinue } } # --------------------------------------------------------------------------- # Verify # --------------------------------------------------------------------------- $provisioned = Get-AppxProvisionedPackage -Online -ErrorAction SilentlyContinue | Where-Object { $_.DisplayName -like "$PackageNameFilter*" -or $_.PackageName -like "$PackageNameFilter*" } $perUser = Get-AppxPackage -AllUsers -Name $PackageNameFilter -ErrorAction SilentlyContinue if ($provisioned -or $perUser) { Write-Output "Install complete." if ($provisioned) { $provisioned | ForEach-Object { Write-Output " Provisioned: $($_.DisplayName) Version=$($_.Version) Arch=$($_.Architecture)" } } if ($perUser) { $perUser | Select-Object -First 3 | ForEach-Object { Write-Output " User package: $($_.PackageFullName)" } } Write-Output "Note: Users already logged on may need to sign out/in before the app appears." exit 0 } else { Write-Error "Provisioning reported success but package was not found afterward." exit 1 }