mirror of
https://github.com/DylanDavis1/DSCourier
synced 2026-06-06 15:34:32 +00:00
381 lines
17 KiB
PowerShell
381 lines
17 KiB
PowerShell
param([string]$OutputDir = "release")
|
|
|
|
$ErrorActionPreference = "Continue"
|
|
$root = $PSScriptRoot
|
|
if (-not $root) { $root = (Get-Location).Path }
|
|
|
|
Write-Host ""
|
|
Write-Host "DSCourier Build"
|
|
Write-Host ""
|
|
|
|
if (-not (Get-Command dotnet -ErrorAction SilentlyContinue)) { Write-Host "[!] .NET SDK not found"; exit 1 }
|
|
if (-not (Get-Command git -ErrorAction SilentlyContinue)) { Write-Host "[!] Git not found"; exit 1 }
|
|
|
|
$wingetPkg = Get-AppxPackage *DesktopAppInstaller* -ErrorAction SilentlyContinue
|
|
if (-not $wingetPkg) { Write-Host "[!] WinGet not found"; exit 1 }
|
|
$wingetPath = $wingetPkg.InstallLocation
|
|
|
|
$configWinmd = Join-Path $wingetPath "Microsoft.Management.Configuration.winmd"
|
|
if (-not (Test-Path $configWinmd)) { Write-Host "[!] Configuration.winmd not found, update WinGet"; exit 1 }
|
|
|
|
$srcCsproj = Get-ChildItem $root -Recurse -Filter "DSCourier.csproj" -ErrorAction SilentlyContinue | Select-Object -First 1
|
|
if (-not $srcCsproj) { Write-Host "[!] DSCourier.csproj not found"; exit 1 }
|
|
|
|
Write-Host "[+] .NET $(dotnet --version) | WinGet v$($wingetPkg.Version)"
|
|
|
|
$interopDir = Join-Path $root "WinGet-API-from-CSharp"
|
|
if (-not (Test-Path (Join-Path $interopDir ".git"))) {
|
|
if (Test-Path $interopDir) { Remove-Item $interopDir -Recurse -Force }
|
|
$env:GIT_TERMINAL_PROMPT = "0"
|
|
git clone --quiet https://github.com/marticliment/WinGet-API-from-CSharp.git $interopDir 2>$null
|
|
if (-not (Test-Path (Join-Path $interopDir ".git"))) { Write-Host "[!] Clone failed"; exit 1 }
|
|
Write-Host "[+] Cloned interop"
|
|
} else {
|
|
Write-Host "[+] Interop already cloned"
|
|
}
|
|
|
|
$csprojPath = Join-Path $interopDir "WindowsPackageManager Interop\WindowsPackageManager Interop.csproj"
|
|
$csprojContent = Get-Content $csprojPath -Raw
|
|
|
|
if ($csprojContent -notmatch 'Microsoft\.Management\.Configuration') {
|
|
$patchedCsproj = @"
|
|
<Project Sdk="Microsoft.NET.Sdk">
|
|
<PropertyGroup>
|
|
<TargetFramework>net8.0-windows10.0.22621.0</TargetFramework>
|
|
<TargetPlatformMinVersion>10.0.22621.0</TargetPlatformMinVersion>
|
|
<SupportedOSPlatformVersion>10.0.22621.0</SupportedOSPlatformVersion>
|
|
<RootNamespace>DevHome.SetupFlow.Common</RootNamespace>
|
|
<Platforms>x86;x64;arm64</Platforms>
|
|
<RuntimeIdentifiers>win-x86;win-x64;win-arm64</RuntimeIdentifiers>
|
|
<Nullable>disable</Nullable>
|
|
</PropertyGroup>
|
|
<PropertyGroup>
|
|
<CsWinRTWindowsMetadata>10.0.22621.0</CsWinRTWindowsMetadata>
|
|
<CsWinRTIncludes>Microsoft.Management.Deployment;Microsoft.Management.Configuration</CsWinRTIncludes>
|
|
</PropertyGroup>
|
|
<ItemGroup>
|
|
<CsWinRTInputs Include="`$(TargetDir)\Microsoft.Management.Deployment.winmd" />
|
|
<CsWinRTInputs Include="`$(TargetDir)\Microsoft.Management.Configuration.winmd" />
|
|
<Content Include="`$(TargetDir)\Microsoft.Management.Deployment.winmd" Link="Microsoft.Management.Deployment.winmd" CopyToOutputDirectory="PreserveNewest" />
|
|
<Content Include="`$(TargetDir)\Microsoft.Management.Configuration.winmd" Link="Microsoft.Management.Configuration.winmd" CopyToOutputDirectory="PreserveNewest" />
|
|
<Content Include="`$(TargetDir)\winrtact.dll" Link="winrtact.dll" CopyToOutputDirectory="PreserveNewest" />
|
|
</ItemGroup>
|
|
<ItemGroup>
|
|
<PackageReference Include="Microsoft.Windows.CsWinRT" Version="2.0.4" />
|
|
<PackageReference Include="Microsoft.Windows.CsWin32" Version="0.3.49-beta">
|
|
<PrivateAssets>all</PrivateAssets>
|
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
|
|
</PackageReference>
|
|
<PackageReference Include="Microsoft.WindowsPackageManager.ComInterop" Version="1.5.1572">
|
|
<NoWarn>NU1701</NoWarn>
|
|
<GeneratePathProperty>true</GeneratePathProperty>
|
|
<IncludeAssets>none</IncludeAssets>
|
|
</PackageReference>
|
|
</ItemGroup>
|
|
<Target Name="CopyWinmdToTargetDir" BeforeTargets="BeforeBuild">
|
|
<Copy SourceFiles="`$(PkgMicrosoft_WindowsPackageManager_ComInterop)\lib\Microsoft.Management.Deployment.winmd" DestinationFolder="`$(TargetDir)" />
|
|
<Copy SourceFiles="$configWinmd" DestinationFolder="`$(TargetDir)" />
|
|
<Copy SourceFiles="`$(PkgMicrosoft_WindowsPackageManager_ComInterop)\runtimes\win10-`$(Platform)\native\winrtact.dll" DestinationFolder="`$(TargetDir)" />
|
|
</Target>
|
|
</Project>
|
|
"@
|
|
[System.IO.File]::WriteAllText($csprojPath, $patchedCsproj, [System.Text.UTF8Encoding]::new($false))
|
|
Write-Host "[+] Patched interop csproj"
|
|
} else {
|
|
Write-Host "[+] Interop csproj already patched"
|
|
}
|
|
|
|
$classDefPath = Join-Path $interopDir "WindowsPackageManager Interop\WindowsPackageManager\ClassesDefinition.cs"
|
|
$classDefContent = Get-Content $classDefPath -Raw
|
|
|
|
if ($classDefContent -notmatch 'ConfigurationStaticFunctions') {
|
|
$patchedClassDef = @'
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using Microsoft.Management.Deployment;
|
|
using Microsoft.Management.Configuration;
|
|
|
|
namespace WindowsPackageManager.Interop;
|
|
|
|
internal static class ClassesDefinition
|
|
{
|
|
private static Dictionary<Type, ClassModel> Classes { get; } = new()
|
|
{
|
|
[typeof(PackageManager)] = new()
|
|
{
|
|
ProjectedClassType = typeof(PackageManager),
|
|
InterfaceType = typeof(IPackageManager),
|
|
Clsids = new Dictionary<ClsidContext, Guid>()
|
|
{
|
|
[ClsidContext.Prod] = new Guid("C53A4F16-787E-42A4-B304-29EFFB4BF597"),
|
|
[ClsidContext.Dev] = new Guid("74CB3139-B7C5-4B9E-9388-E6616DEA288C"),
|
|
},
|
|
},
|
|
[typeof(FindPackagesOptions)] = new()
|
|
{
|
|
ProjectedClassType = typeof(FindPackagesOptions),
|
|
InterfaceType = typeof(IFindPackagesOptions),
|
|
Clsids = new Dictionary<ClsidContext, Guid>()
|
|
{
|
|
[ClsidContext.Prod] = new Guid("572DED96-9C60-4526-8F92-EE7D91D38C1A"),
|
|
[ClsidContext.Dev] = new Guid("1BD8FF3A-EC50-4F69-AEEE-DF4C9D3BAA96"),
|
|
},
|
|
},
|
|
[typeof(CreateCompositePackageCatalogOptions)] = new()
|
|
{
|
|
ProjectedClassType = typeof(CreateCompositePackageCatalogOptions),
|
|
InterfaceType = typeof(ICreateCompositePackageCatalogOptions),
|
|
Clsids = new Dictionary<ClsidContext, Guid>()
|
|
{
|
|
[ClsidContext.Prod] = new Guid("526534B8-7E46-47C8-8416-B1685C327D37"),
|
|
[ClsidContext.Dev] = new Guid("EE160901-B317-4EA7-9CC6-5355C6D7D8A7"),
|
|
},
|
|
},
|
|
[typeof(InstallOptions)] = new()
|
|
{
|
|
ProjectedClassType = typeof(InstallOptions),
|
|
InterfaceType = typeof(IInstallOptions),
|
|
Clsids = new Dictionary<ClsidContext, Guid>()
|
|
{
|
|
[ClsidContext.Prod] = new Guid("1095F097-EB96-453B-B4E6-1613637F3B14"),
|
|
[ClsidContext.Dev] = new Guid("44FE0580-62F7-44D4-9E91-AA9614AB3E86"),
|
|
},
|
|
},
|
|
[typeof(UninstallOptions)] = new()
|
|
{
|
|
ProjectedClassType = typeof(UninstallOptions),
|
|
InterfaceType = typeof(IUninstallOptions),
|
|
Clsids = new Dictionary<ClsidContext, Guid>()
|
|
{
|
|
[ClsidContext.Prod] = new Guid("E1D9A11E-9F85-4D87-9C17-2B93143ADB8D"),
|
|
[ClsidContext.Dev] = new Guid("AA2A5C04-1AD9-46C4-B74F-6B334AD7EB8C"),
|
|
},
|
|
},
|
|
[typeof(PackageMatchFilter)] = new()
|
|
{
|
|
ProjectedClassType = typeof(PackageMatchFilter),
|
|
InterfaceType = typeof(IPackageMatchFilter),
|
|
Clsids = new Dictionary<ClsidContext, Guid>()
|
|
{
|
|
[ClsidContext.Prod] = new Guid("D02C9DAF-99DC-429C-B503-4E504E4AB000"),
|
|
[ClsidContext.Dev] = new Guid("3F85B9F4-487A-4C48-9035-2903F8A6D9E8"),
|
|
},
|
|
},
|
|
[typeof(ConfigurationStaticFunctions)] = new()
|
|
{
|
|
ProjectedClassType = typeof(ConfigurationStaticFunctions),
|
|
InterfaceType = typeof(IConfigurationStatics),
|
|
Clsids = new Dictionary<ClsidContext, Guid>()
|
|
{
|
|
[ClsidContext.Prod] = new Guid("73D763B7-2937-432F-A97A-D98A4A596126"),
|
|
[ClsidContext.Dev] = new Guid("73D763B7-2937-432F-A97A-D98A4A596126"),
|
|
},
|
|
},
|
|
};
|
|
|
|
public static Guid GetClsid<T>(ClsidContext context)
|
|
{
|
|
ValidateType<T>();
|
|
return Classes[typeof(T)].GetClsid(context);
|
|
}
|
|
|
|
public static Guid GetIid<T>()
|
|
{
|
|
ValidateType<T>();
|
|
return Classes[typeof(T)].GetIid();
|
|
}
|
|
|
|
private static void ValidateType<TType>()
|
|
{
|
|
if (!Classes.ContainsKey(typeof(TType)))
|
|
{
|
|
throw new InvalidOperationException($"{typeof(TType).Name} is not a projected class type.");
|
|
}
|
|
}
|
|
}
|
|
'@
|
|
[System.IO.File]::WriteAllText($classDefPath, $patchedClassDef, [System.Text.UTF8Encoding]::new($false))
|
|
Write-Host "[+] Patched ClassesDefinition.cs"
|
|
} else {
|
|
Write-Host "[+] ClassesDefinition.cs already patched"
|
|
}
|
|
|
|
$factoryPath = Join-Path $interopDir "WindowsPackageManager Interop\WindowsPackageManager\WindowsPackageManagerFactory.cs"
|
|
$factoryContent = Get-Content $factoryPath -Raw
|
|
|
|
if ($factoryContent -notmatch 'CreateConfigurationStaticFunctions') {
|
|
$patchedFactory = @'
|
|
using System;
|
|
using Microsoft.Management.Deployment;
|
|
using Microsoft.Management.Configuration;
|
|
|
|
namespace WindowsPackageManager.Interop;
|
|
|
|
public abstract class WindowsPackageManagerFactory
|
|
{
|
|
private readonly ClsidContext _clsidContext;
|
|
|
|
public WindowsPackageManagerFactory(ClsidContext clsidContext)
|
|
{
|
|
_clsidContext = clsidContext;
|
|
}
|
|
|
|
protected abstract T CreateInstance<T>(Guid clsid, Guid iid);
|
|
|
|
public PackageManager CreatePackageManager() => CreateInstance<PackageManager>();
|
|
public FindPackagesOptions CreateFindPackagesOptions() => CreateInstance<FindPackagesOptions>();
|
|
public CreateCompositePackageCatalogOptions CreateCreateCompositePackageCatalogOptions() => CreateInstance<CreateCompositePackageCatalogOptions>();
|
|
public InstallOptions CreateInstallOptions() => CreateInstance<InstallOptions>();
|
|
public UninstallOptions CreateUninstallOptions() => CreateInstance<UninstallOptions>();
|
|
public PackageMatchFilter CreatePackageMatchFilter() => CreateInstance<PackageMatchFilter>();
|
|
public ConfigurationStaticFunctions CreateConfigurationStaticFunctions() => CreateInstance<ConfigurationStaticFunctions>();
|
|
|
|
private T CreateInstance<T>()
|
|
{
|
|
var clsid = ClassesDefinition.GetClsid<T>(_clsidContext);
|
|
var iid = ClassesDefinition.GetIid<T>();
|
|
return CreateInstance<T>(clsid, iid);
|
|
}
|
|
}
|
|
'@
|
|
[System.IO.File]::WriteAllText($factoryPath, $patchedFactory, [System.Text.UTF8Encoding]::new($false))
|
|
Write-Host "[+] Patched Factory"
|
|
} else {
|
|
Write-Host "[+] Factory already patched"
|
|
}
|
|
|
|
Write-Host "[*] Building interop..."
|
|
Push-Location $interopDir
|
|
$buildOutput = & dotnet build "WindowsPackageManager Interop\WindowsPackageManager Interop.csproj" -p:Platform=x64 2>&1 | Out-String
|
|
Pop-Location
|
|
|
|
if ($buildOutput -notmatch 'Build succeeded') {
|
|
$errors = ($buildOutput -split "`n") | Where-Object { $_ -match ': error ' }
|
|
if ($errors) {
|
|
Write-Host "[!] Interop build failed:"
|
|
$errors | ForEach-Object { Write-Host " $_" }
|
|
exit 1
|
|
}
|
|
}
|
|
Write-Host "[+] Interop built"
|
|
|
|
$interopProjDir = Join-Path $interopDir "WindowsPackageManager Interop"
|
|
$interopDll = Get-ChildItem $interopProjDir -Recurse -Filter "WindowsPackageManager Interop.dll" -ErrorAction SilentlyContinue |
|
|
Where-Object { $_.FullName -match 'bin' } | Select-Object -First 1
|
|
if (-not $interopDll) { Write-Host "[!] Interop DLL not found"; exit 1 }
|
|
$interopBinDir = $interopDll.DirectoryName
|
|
|
|
Write-Host "[*] Building DSCourier..."
|
|
$srcDir = $srcCsproj.DirectoryName
|
|
$originalCsproj = Get-Content $srcCsproj.FullName -Raw
|
|
|
|
$clientCsproj = $originalCsproj
|
|
$clientCsproj = $clientCsproj -replace '<HintPath>[^<]+</HintPath>', "<HintPath>$($interopDll.FullName)</HintPath>"
|
|
[System.IO.File]::WriteAllText($srcCsproj.FullName, $clientCsproj, [System.Text.UTF8Encoding]::new($false))
|
|
|
|
Push-Location $srcDir
|
|
Remove-Item ".\bin" -Recurse -Force -ErrorAction SilentlyContinue
|
|
Remove-Item ".\obj" -Recurse -Force -ErrorAction SilentlyContinue
|
|
& dotnet build -c Release 2>&1 | Out-Null
|
|
Pop-Location
|
|
|
|
$exeName = "DSCourier.exe"
|
|
$asmMatch = [regex]::Match($clientCsproj, '<AssemblyName>([^<]+)</AssemblyName>')
|
|
if ($asmMatch.Success) { $exeName = "$($asmMatch.Groups[1].Value).exe" }
|
|
$asmName = $exeName -replace '\.exe$', ''
|
|
|
|
$builtExe = Get-ChildItem $srcDir -Recurse -Filter $exeName -ErrorAction SilentlyContinue |
|
|
Where-Object { $_.FullName -match 'bin' } | Select-Object -First 1
|
|
if (-not $builtExe) { Write-Host "[!] $exeName not found"; exit 1 }
|
|
$exeBinDir = $builtExe.DirectoryName
|
|
Write-Host "[+] DSCourier built"
|
|
|
|
Write-Host "[*] Trimming SDK..."
|
|
|
|
$trimmerRoots = @'
|
|
<linker>
|
|
<assembly fullname="Microsoft.Windows.SDK.NET">
|
|
<type fullname="System.IO.WindowsRuntimeStreamExtensions" preserve="all" />
|
|
<type fullname="Windows.Storage.Streams.*" preserve="all" />
|
|
<type fullname="System.Runtime.InteropServices.WindowsRuntime.*" preserve="all" />
|
|
</assembly>
|
|
</linker>
|
|
'@
|
|
$trimmerPath = Join-Path $srcDir "TrimmerRoots.xml"
|
|
[System.IO.File]::WriteAllText($trimmerPath, $trimmerRoots, [System.Text.UTF8Encoding]::new($false))
|
|
|
|
$trimCsproj = Get-Content $srcCsproj.FullName -Raw
|
|
$trimCsproj = $trimCsproj -replace '</PropertyGroup>', " <PublishTrimmed>true</PublishTrimmed>`n <SelfContained>true</SelfContained>`n <TrimMode>link</TrimMode>`n </PropertyGroup>"
|
|
if ($trimCsproj -notmatch 'TrimmerRootDescriptor') {
|
|
$trimCsproj = $trimCsproj -replace '</Project>', " <ItemGroup>`n <TrimmerRootDescriptor Include=`"TrimmerRoots.xml`" />`n </ItemGroup>`n</Project>"
|
|
}
|
|
[System.IO.File]::WriteAllText($srcCsproj.FullName, $trimCsproj, [System.Text.UTF8Encoding]::new($false))
|
|
|
|
Push-Location $srcDir
|
|
Remove-Item ".\bin" -Recurse -Force -ErrorAction SilentlyContinue
|
|
Remove-Item ".\obj" -Recurse -Force -ErrorAction SilentlyContinue
|
|
& dotnet publish -c Release 2>&1 | Out-Null
|
|
Pop-Location
|
|
|
|
$trimmedSdk = Get-ChildItem $srcDir -Recurse -Filter "Microsoft.Windows.SDK.NET.dll" -ErrorAction SilentlyContinue |
|
|
Where-Object { $_.FullName -match 'publish' -and $_.Length -lt 1MB } | Select-Object -First 1
|
|
|
|
$trimmedSdkPath = $null
|
|
if ($trimmedSdk) {
|
|
$trimmedSdkPath = Join-Path $root "trimmed-sdk.dll"
|
|
Copy-Item $trimmedSdk.FullName -Destination $trimmedSdkPath -Force
|
|
$kb = [math]::Round($trimmedSdk.Length / 1KB, 1)
|
|
Write-Host "[+] Trimmed SDK: $kb KB"
|
|
} else {
|
|
Write-Host "[!] Trim failed, using full SDK"
|
|
}
|
|
|
|
[System.IO.File]::WriteAllText($srcCsproj.FullName, $clientCsproj, [System.Text.UTF8Encoding]::new($false))
|
|
|
|
Push-Location $srcDir
|
|
Remove-Item ".\bin" -Recurse -Force -ErrorAction SilentlyContinue
|
|
Remove-Item ".\obj" -Recurse -Force -ErrorAction SilentlyContinue
|
|
& dotnet build -c Release 2>&1 | Out-Null
|
|
Pop-Location
|
|
|
|
$builtExe = Get-ChildItem $srcDir -Recurse -Filter $exeName -ErrorAction SilentlyContinue |
|
|
Where-Object { $_.FullName -match 'bin' } | Select-Object -First 1
|
|
$exeBinDir = $builtExe.DirectoryName
|
|
|
|
Write-Host "[*] Assembling release..."
|
|
|
|
$releaseDir = Join-Path $root $OutputDir
|
|
if (Test-Path $releaseDir) { Remove-Item $releaseDir -Recurse -Force }
|
|
New-Item -Path $releaseDir -ItemType Directory -Force | Out-Null
|
|
|
|
foreach ($f in @("$exeName", "$asmName.dll", "$asmName.runtimeconfig.json")) {
|
|
$src = Join-Path $exeBinDir $f
|
|
if (Test-Path $src) { Copy-Item $src -Destination $releaseDir -Force; Write-Host " [+] $f" }
|
|
}
|
|
|
|
foreach ($f in @("WindowsPackageManager Interop.dll", "winrtact.dll", "WinRT.Runtime.dll", "Microsoft.Windows.SDK.NET.dll", "Microsoft.Management.Configuration.winmd", "Microsoft.Management.Deployment.winmd")) {
|
|
$src = Join-Path $interopBinDir $f
|
|
if (-not (Test-Path $src)) { $src = Join-Path $exeBinDir $f }
|
|
if (Test-Path $src) { Copy-Item $src -Destination $releaseDir -Force; Write-Host " [+] $f" }
|
|
else { Write-Host " [!] Missing: $f" }
|
|
}
|
|
|
|
if ($trimmedSdkPath -and (Test-Path $trimmedSdkPath)) {
|
|
Copy-Item $trimmedSdkPath -Destination (Join-Path $releaseDir "Microsoft.Windows.SDK.NET.dll") -Force
|
|
Remove-Item $trimmedSdkPath -Force
|
|
Write-Host " [+] Swapped in trimmed SDK"
|
|
}
|
|
|
|
Write-Host ""
|
|
Write-Host "Build complete: $releaseDir"
|
|
Write-Host ""
|
|
$totalSize = 0
|
|
Get-ChildItem $releaseDir -File | ForEach-Object {
|
|
$kb = [math]::Round($_.Length / 1KB, 1)
|
|
$totalSize += $_.Length
|
|
Write-Host " $($_.Name) ($kb KB)"
|
|
}
|
|
Write-Host ""
|
|
$totalMb = [math]::Round($totalSize / 1MB, 2)
|
|
Write-Host " Total: $totalMb MB"
|
|
Write-Host "" |