Tools/Export-VCRuntimeForPSF.ps1

<#
.SYNOPSIS
    Copies the VC++ runtime DLLs the PSF fixups need from this machine into a Desktop folder.

.DESCRIPTION
    The PSF fixup DLLs (FileRedirection, MFR, RegLegacy, DynamicLibrary, EnvVar) import
    msvcp140.dll / vcruntime140.dll (x64 also vcruntime140_1.dll). Tim Mangan's PSF does not ship
    them, so Update-MSIXTMPSF takes them from System32/SysWOW64 - which fails on a packaging
    machine without the VC++ Redistributable. This script collects them on a machine that has
    them, in the PSF folder layout (amd64\ = x64 from System32, win32\ = x86 from SysWOW64).
    Copy both folders into each PSF build folder afterwards, e.g.
    <Modules>\MSIXForcelets\Tooling\MSIXPSF\TimManganPSF\2026.10.13_release\
    msvcp140_1/_2 are not imported by the PSF but expected by Add-MSIXPsfFrameworkFiles, so
    they are collected as well.

.EXAMPLE
    & (Join-Path (Get-Module MSIXForcelets -ListAvailable | Select-Object -First 1).ModuleBase 'Tools\Export-VCRuntimeForPSF.ps1')

.NOTES
    https://www.nick-it.de
    Andreas Nick, 2026
#>

[CmdletBinding()]
param(
    [string] $Destination = (Join-Path ([Environment]::GetFolderPath('Desktop')) 'VCRuntime-PSF'),
    [switch] $Force
)

$ErrorActionPreference = 'Stop'

# A 32-bit PowerShell on 64-bit Windows sees SysWOW64 behind "System32"; Sysnative is the real one.
$system64 = Join-Path $env:windir 'System32'
if ([Environment]::Is64BitOperatingSystem -and -not [Environment]::Is64BitProcess) {
    $system64 = Join-Path $env:windir 'Sysnative'
}

$sets = @(
    @{ Folder = 'amd64'; Source = $system64;                         Machine = 0x8664
       Files  = @('msvcp140.dll', 'msvcp140_1.dll', 'msvcp140_2.dll', 'vcruntime140.dll', 'vcruntime140_1.dll') }
    @{ Folder = 'win32'; Source = (Join-Path $env:windir 'SysWOW64'); Machine = 0x014C
       Files  = @('msvcp140.dll', 'msvcp140_1.dll', 'msvcp140_2.dll', 'vcruntime140.dll') }
)

# Reads the machine type from the PE header (0x8664 = x64, 0x014C = x86).
function Get-PEMachine([string] $Path) {
    $bytes = [System.IO.File]::ReadAllBytes($Path)
    $peOffset = [BitConverter]::ToInt32($bytes, 0x3C)
    return [BitConverter]::ToUInt16($bytes, $peOffset + 4)
}

if ((Test-Path -LiteralPath $Destination) -and -not $Force) {
    throw "Destination already exists: $Destination (use -Force to overwrite)."
}

$missing = 0
$report  = foreach ($set in $sets) {
    $targetDir = Join-Path $Destination $set.Folder
    $null = New-Item -ItemType Directory -Path $targetDir -Force
    foreach ($name in $set.Files) {
        $source = Join-Path $set.Source $name
        if (-not (Test-Path -LiteralPath $source)) {
            Write-Warning "Not found on this machine: $source"
            $missing++
            continue
        }
        $machine = Get-PEMachine $source
        if ($machine -ne $set.Machine) {
            Write-Warning ("Skipped {0}: wrong architecture (0x{1:X4}) for {2}\" -f $source, $machine, $set.Folder)
            $missing++
            continue
        }
        Copy-Item -LiteralPath $source -Destination $targetDir -Force
        $version = (Get-Item -LiteralPath $source).VersionInfo.FileVersion
        Write-Verbose "Copied $source -> $($set.Folder)\"
        [pscustomobject]@{ Folder = $set.Folder; File = $name; Version = $version }
    }
}

# Keep a record of where the files came from (version of the installed redistributable).
$info = Join-Path $Destination 'SOURCE.txt'
@(
    "VC++ runtime DLLs for the MSIX Package Support Framework"
    "Collected from: $env:COMPUTERNAME on $(Get-Date -Format 'yyyy-MM-dd HH:mm')"
    ''
    ($report | Format-Table -AutoSize | Out-String).TrimEnd()
) | Set-Content -LiteralPath $info -Encoding UTF8

$report | Format-Table -AutoSize
if ($missing -gt 0) {
    Write-Warning "$missing file(s) missing - install the VC++ Redistributable (x64 and x86) on this machine and run again."
}
Write-Output "Done: $Destination - copy amd64\ and win32\ into each PSF build folder (..._release and ..._debug)."