Files
lserp_cs_6.0/插件库/Lskj.AgentBridge/Deployment/Build-LegacyErpAcceptance.ps1
2026-08-14 14:28:28 +08:00

1010 lines
47 KiB
PowerShell

[CmdletBinding(DefaultParameterSetName = 'Build')]
param(
[Parameter(Mandatory = $true)][string]$RepoRoot,
[Parameter(Mandatory = $true, ParameterSetName = 'Build')][string]$OutputDirectory,
[Parameter(Mandatory = $true, ParameterSetName = 'Build')]
[ValidatePattern('^[A-Fa-f0-9]{40}$')][string]$ExpectedSourceCommit,
[Parameter(ParameterSetName = 'Build')][string]$MSBuildPath = '',
[Parameter(ParameterSetName = 'Build')][string]$EditBinPath = '',
[Parameter(ParameterSetName = 'Build')][string]$CefRedistPackagePath = '',
[Parameter(ParameterSetName = 'Build')][string]$SignToolPath = '',
[Parameter(ParameterSetName = 'Build')]
[ValidatePattern('^$|^[A-Fa-f0-9]{40}$')][string]$AuthenticodeCertificateThumbprint = '',
[Parameter(ParameterSetName = 'Build')]
[ValidateSet('CurrentUser', 'LocalMachine')][string]$CertificateStoreLocation = 'CurrentUser',
[Parameter(ParameterSetName = 'Build')][string]$TimestampUrl = '',
[Parameter(Mandatory = $true, ParameterSetName = 'Inspect')]
[switch]$ValidateRepositoryOnly
)
Set-StrictMode -Version 2.0
$ErrorActionPreference = 'Stop'
$CefRedistPackageId = 'cef.redist.x86'
$CefRedistPackageVersion = '87.1.13'
$CefRedistPackageSha256 = `
'34dfe2504c1ffaef02eab1f38578701b045439349997b6465fd5dd6659fab021'
$CefGlueSha256 = `
'5057f66d83727e73ce926f918c459fa4a29c9e0a78a2654c09e58210f4355cd7'
$CefLibcefSha256 = `
'a8d4c9974cbdfc989f9c993cba0a388d69e2c5c009661b2c064baaf016e84cfd'
function Test-RegularFile([string]$Path, [long]$MaximumBytes) {
if (-not [IO.File]::Exists($Path)) { return $false }
$item = Get-Item -LiteralPath $Path -Force
return $item.Length -gt 0 -and $item.Length -le $MaximumBytes -and
(($item.Attributes -band [IO.FileAttributes]::ReparsePoint) -eq 0)
}
function Assert-NoHardcodedSqlCredentials([string]$RuntimeDirectory) {
if (-not [IO.Directory]::Exists($RuntimeDirectory)) {
throw 'legacy_runtime_directory_missing'
}
$pattern =
'(?i)(?:User\s+ID|UID)\s*=\s*[A-Za-z0-9_.@\-]{1,128}\s*;\s*' +
'(?:Password|PWD)\s*=\s*[^;\s"''{}+]{1,256}\s*;'
$candidates = @(Get-ChildItem -LiteralPath $RuntimeDirectory -File -Force |
Where-Object {
$_.Name -ieq 'Ls_ERP.exe' -or
$_.Name -ieq 'lserp-cli.exe' -or
($_.Name -ilike 'Lskj.*.dll')
} | Sort-Object Name)
if ($candidates.Count -lt 4 -or $candidates.Count -gt 256) {
throw 'legacy_runtime_credential_scan_scope_invalid'
}
$latin1 = [Text.Encoding]::GetEncoding(28591)
foreach ($candidate in $candidates) {
if (-not (Test-RegularFile $candidate.FullName 128MB)) {
throw ('legacy_runtime_credential_scan_file_invalid:' +
$candidate.Name)
}
[byte[]]$bytes = [IO.File]::ReadAllBytes($candidate.FullName)
$views = @(
$latin1.GetString($bytes),
[Text.Encoding]::Unicode.GetString($bytes),
[Text.Encoding]::BigEndianUnicode.GetString($bytes)
)
foreach ($view in $views) {
if ([Text.RegularExpressions.Regex]::IsMatch(
$view,
$pattern,
[Text.RegularExpressions.RegexOptions]::CultureInvariant)) {
throw ('legacy_runtime_hardcoded_sql_credential:' +
$candidate.Name)
}
}
}
}
function Get-RelativePath([string]$Root, [string]$Path) {
$rootFull = [IO.Path]::GetFullPath($Root).TrimEnd([char[]]@('\', '/'))
$pathFull = [IO.Path]::GetFullPath($Path)
$prefix = $rootFull + [IO.Path]::DirectorySeparatorChar
$comparison = if ($env:OS -eq 'Windows_NT') {
[StringComparison]::OrdinalIgnoreCase
}
else {
[StringComparison]::Ordinal
}
if (-not $pathFull.StartsWith($prefix, $comparison)) {
throw 'relative_path_outside_root'
}
return $pathFull.Substring($prefix.Length).Replace('\', '/')
}
function Get-ProjectContract([string]$Path) {
if (-not (Test-RegularFile $Path 4MB)) {
throw "project_file_invalid"
}
$text = [IO.File]::ReadAllText($Path, (New-Object Text.UTF8Encoding($false, $true)))
$xml = New-Object Xml.XmlDocument
$xml.PreserveWhitespace = $true
$xml.LoadXml($text)
$namespace = New-Object Xml.XmlNamespaceManager($xml.NameTable)
$namespace.AddNamespace('m', 'http://schemas.microsoft.com/developer/msbuild/2003')
$assembly = $xml.SelectSingleNode('//m:AssemblyName', $namespace)
$framework = $xml.SelectSingleNode('//m:TargetFrameworkVersion', $namespace)
$platforms = @($xml.SelectNodes('//m:PlatformTarget', $namespace) | ForEach-Object {
([string]$_.InnerText).Trim()
} | Select-Object -Unique)
$references = @($xml.SelectNodes('//m:ProjectReference/@Include', $namespace) | ForEach-Object {
([string]$_.Value).Replace('/', '\')
})
$compile = @($xml.SelectNodes('//m:Compile/@Include', $namespace) | ForEach-Object {
([string]$_.Value).Replace('/', '\')
})
if ($null -eq $assembly -or $null -eq $framework -or $platforms.Count -eq 0) {
throw "project_contract_incomplete"
}
return [ordered]@{
assemblyName = ([string]$assembly.InnerText).Trim()
targetFramework = ([string]$framework.InnerText).Trim()
platformTargets = $platforms
projectReferences = $references
compileItems = $compile
}
}
function Assert-Contains([object[]]$Values, [string]$Expected, [string]$Code) {
if ($Values -notcontains $Expected) { throw $Code }
}
function Test-RepositoryContract([string]$Root) {
$paths = [ordered]@{
solution = Join-Path $Root '插件库\Lskj.LserpAll\Lskj.LserpAll.sln'
main = Join-Path $Root '插件库\Lskj.Main\Lskj.Main.csproj'
cli = Join-Path $Root '插件库\Lskj.Cli\Lskj.Cli.csproj'
bridge = Join-Path $Root '插件库\Lskj.AgentBridge\Lskj.AgentBridge.csproj'
kernel = Join-Path $Root '插件库\Lskj.CommandKernel\Lskj.CommandKernel.csproj'
business = Join-Path $Root '插件库\Lskj.Business\Lskj.Business.csproj'
control = Join-Path $Root '插件库\Lskj.Control\Lskj.Control.csproj'
compatibility = Join-Path $Root `
'插件库\Lskj.LegacyApiCompatibility.Tests\Lskj.LegacyApiCompatibility.Tests.csproj'
}
$main = Get-ProjectContract $paths.main
$cli = Get-ProjectContract $paths.cli
$bridge = Get-ProjectContract $paths.bridge
$kernel = Get-ProjectContract $paths.kernel
if ($main.assemblyName -ne 'Ls_ERP' -or $cli.assemblyName -ne 'lserp-cli' -or
$bridge.assemblyName -ne 'Lskj.AgentBridge' -or
$kernel.assemblyName -ne 'Lskj.CommandKernel') {
throw 'legacy_assembly_name_mismatch'
}
foreach ($project in @($main, $cli, $bridge, $kernel)) {
if ($project.targetFramework -ne 'v4.0' -or
$project.platformTargets.Count -ne 1 -or
$project.platformTargets[0] -ne 'x86') {
throw 'legacy_framework_or_platform_mismatch'
}
}
Assert-Contains $main.projectReferences '..\Lskj.AgentBridge\Lskj.AgentBridge.csproj' `
'main_bridge_reference_missing'
Assert-Contains $main.projectReferences '..\Lskj.CommandKernel\Lskj.CommandKernel.csproj' `
'main_kernel_reference_missing'
Assert-Contains $main.compileItems 'Hosting\ErpAgentBridgeBootstrap.cs' `
'main_bridge_bootstrap_missing'
Assert-Contains $main.compileItems 'Hosting\ModuleDiagnosticCommandHandlers.cs' `
'main_module_diagnostic_handlers_missing'
Assert-Contains $main.compileItems 'Hosting\BusinessWorkflowRegistration.cs' `
'main_workflow_registration_missing'
Assert-Contains $main.compileItems 'Hosting\SqlWorkflowProcedureGateway.cs' `
'main_workflow_gateway_missing'
Assert-Contains $main.compileItems 'Hosting\SqlDynamicModuleWriteAdapter.cs' `
'main_dynamic_module_write_adapter_missing'
Assert-Contains $main.compileItems 'Hosting\DynamicModuleWriteAvailability.cs' `
'main_dynamic_module_write_availability_missing'
Assert-Contains $main.compileItems 'Hosting\SqlDynamicModuleUpdateAdapter.cs' `
'main_dynamic_module_update_adapter_missing'
Assert-Contains $main.compileItems 'Hosting\DynamicModuleUpdateAvailability.cs' `
'main_dynamic_module_update_availability_missing'
Assert-Contains $main.compileItems 'Hosting\DynamicModuleUpdateCommandHandlers.cs' `
'main_dynamic_module_update_handlers_missing'
Assert-Contains $cli.projectReferences '..\Lskj.Main\Lskj.Main.csproj' `
'cli_main_reference_missing'
Assert-Contains $cli.projectReferences '..\Lskj.AgentBridge\Lskj.AgentBridge.csproj' `
'cli_bridge_reference_missing'
Assert-Contains $cli.projectReferences '..\Lskj.CommandKernel\Lskj.CommandKernel.csproj' `
'cli_kernel_reference_missing'
Assert-Contains $cli.compileItems 'BridgeCliClient.cs' 'cli_bridge_client_missing'
Assert-Contains $cli.compileItems 'BridgeCommands.cs' 'cli_bridge_commands_missing'
Assert-Contains $cli.compileItems 'WorkflowCommands.cs' `
'cli_workflow_commands_missing'
Assert-Contains $bridge.compileItems 'WorkflowWriteIntegrationEvidence.cs' `
'bridge_write_evidence_verifier_missing'
Assert-Contains $bridge.compileItems 'CustomerAcceptanceBundleEvidence.cs' `
'bridge_customer_acceptance_bundle_verifier_missing'
Assert-Contains $bridge.compileItems 'DynamicModuleWriteAcceptance.cs' `
'bridge_dynamic_module_write_acceptance_missing'
Assert-Contains $bridge.compileItems 'DynamicModuleUpdateAcceptance.cs' `
'bridge_dynamic_module_update_acceptance_missing'
Assert-Contains $kernel.compileItems 'DynamicModuleOperations.cs' `
'kernel_dynamic_module_operations_missing'
Assert-Contains $kernel.compileItems 'DynamicModuleLookupResolution.cs' `
'kernel_dynamic_module_lookup_resolution_missing'
Assert-Contains $kernel.compileItems 'DynamicModuleNativeExecution.cs' `
'kernel_dynamic_module_native_execution_missing'
Assert-Contains $kernel.compileItems 'DynamicModuleWrites.cs' `
'kernel_dynamic_module_writes_missing'
Assert-Contains $kernel.compileItems 'DynamicModuleUpdates.cs' `
'kernel_dynamic_module_updates_missing'
Assert-Contains $kernel.compileItems '..\Lskj.Cli\ModuleInspector.cs' `
'kernel_module_inspector_missing'
$mainProjectText = [IO.File]::ReadAllText(
$paths.main, (New-Object Text.UTF8Encoding($false, $true)))
if (-not $mainProjectText.Contains('$(LegacyEditBinPath)') -or
$mainProjectText -match '(?i)[A-Z]:\\[^\r\n]*editbin\.exe') {
throw 'main_editbin_contract_invalid'
}
foreach ($bindingProjectPath in @($paths.main, $paths.control)) {
if (-not (Test-RegularFile $bindingProjectPath 4MB)) {
throw 'cef_binding_project_missing'
}
$bindingProjectText = [IO.File]::ReadAllText(
$bindingProjectPath, (New-Object Text.UTF8Encoding($false, $true)))
if (-not $bindingProjectText.Contains(
'Xilium.CefGlue, Version=87.1.1.0, Culture=neutral')) {
throw 'cef_glue_reference_contract_invalid'
}
}
if (-not (Test-RegularFile $paths.compatibility 1MB)) {
throw 'net40_compatibility_gate_missing'
}
$compatibilityText = [IO.File]::ReadAllText(
$paths.compatibility, (New-Object Text.UTF8Encoding($false, $true)))
if (-not $compatibilityText.Contains('<TargetFramework>net40</TargetFramework>') -or
-not $compatibilityText.Contains('Microsoft.NETFramework.ReferenceAssemblies.net40') -or
-not $compatibilityText.Contains('ModuleDiagnosticCommandHandlers.cs')) {
throw 'net40_compatibility_gate_invalid'
}
if (-not (Test-RegularFile $paths.solution 8MB)) {
throw 'legacy_solution_missing'
}
$solutionText = [IO.File]::ReadAllText(
$paths.solution, (New-Object Text.UTF8Encoding($false, $true)))
$solutionContracts = @(
'Lskj.Cli", "..\Lskj.Cli\Lskj.Cli.csproj", "{A7D3D7C2-8F1B-4E48-9E30-8AB657CFC104}"',
'Lskj.Main", "..\Lskj.Main\Lskj.Main.csproj", "{BCA1E2B3-C4AB-4D2C-B519-3DCFDB5B83D6}"',
'Lskj.CommandKernel", "..\Lskj.CommandKernel\Lskj.CommandKernel.csproj", "{84D4754E-9D47-4E60-A8E5-0AD860C319F0}"',
'Lskj.AgentBridge", "..\Lskj.AgentBridge\Lskj.AgentBridge.csproj", "{8DA49516-B088-49CC-BE29-2E7EC3CC1777}"',
'{A7D3D7C2-8F1B-4E48-9E30-8AB657CFC104}.Release|Mixed Platforms.ActiveCfg = Release|x86',
'{BCA1E2B3-C4AB-4D2C-B519-3DCFDB5B83D6}.Release|Mixed Platforms.ActiveCfg = Release|x86',
'{84D4754E-9D47-4E60-A8E5-0AD860C319F0}.Release|Mixed Platforms.ActiveCfg = Release|x86',
'{8DA49516-B088-49CC-BE29-2E7EC3CC1777}.Release|Mixed Platforms.ActiveCfg = Release|x86'
)
foreach ($solutionContract in $solutionContracts) {
if (-not $solutionText.Contains($solutionContract)) {
throw 'legacy_solution_configuration_invalid'
}
}
$cefContract = 'packages\cef.redist.x86.87.1.13\build\cef.redist.x86.props'
$cefProjectPaths = @(
$paths.business,
$paths.control,
(Join-Path $Root '插件库\Lskj.AutoCreatWord\Lskj.AutoCreatWord.csproj'),
(Join-Path $Root '插件库\Lskj.EmbeCad\Lskj.EmbeCad.csproj'),
(Join-Path $Root '插件库\Lskj.PubModelAdd2\Lskj.PubModelAdd2.csproj'),
(Join-Path $Root '插件库\Lskj.PubModelAdd3\Lskj.PubModelAdd3.csproj'),
(Join-Path $Root '插件库\Lskj.PubSpec\Lskj.PubSpec.csproj')
)
foreach ($cefProjectPath in $cefProjectPaths) {
if (-not (Test-RegularFile $cefProjectPath 4MB)) {
throw 'cef_redist_project_missing'
}
$cefProjectText = [IO.File]::ReadAllText(
$cefProjectPath, (New-Object Text.UTF8Encoding($false, $true)))
$cefVersions = @([regex]::Matches(
$cefProjectText, 'cef\.redist\.x86\.[0-9]+\.[0-9]+\.[0-9]+') |
ForEach-Object { $_.Value } | Select-Object -Unique)
if (-not $cefProjectText.Contains($cefContract) -or
$cefVersions.Count -ne 1 -or
$cefVersions[0] -ne ('cef.redist.x86.' + $CefRedistPackageVersion)) {
throw 'cef_redist_contract_invalid'
}
}
$cefPackageConfigPaths = @(
(Join-Path $Root '插件库\Lskj.AutoCreatWord\packages.config'),
(Join-Path $Root '插件库\Lskj.Control\packages.config'),
(Join-Path $Root '插件库\Lskj.EmbeCad\packages.config')
)
foreach ($cefPackageConfigPath in $cefPackageConfigPaths) {
if (-not (Test-RegularFile $cefPackageConfigPath 1MB)) {
throw 'cef_redist_package_config_missing'
}
$cefPackageConfigText = [IO.File]::ReadAllText(
$cefPackageConfigPath, (New-Object Text.UTF8Encoding($false, $true)))
if (-not $cefPackageConfigText.Contains(
('id="cef.redist.x86" version="' + $CefRedistPackageVersion + '"'))) {
throw 'cef_redist_package_config_invalid'
}
}
$dependencyFiles = @(
'引用DLL\Newtonsoft.Json.dll',
'引用DLL\Xilium.CefGlue.dll',
'引用DLL\DevExpress\DevExpress.Data.v15.2.dll',
'引用DLL\DevExpress\DevExpress.Utils.v15.2.dll',
'引用DLL\DevExpress\DevExpress.XtraEditors.v15.2.dll',
'引用DLL\DevExpress\DevExpress.XtraGrid.v15.2.dll',
'引用DLL\DevExpress\DevExpress.XtraPrinting.v15.2.dll',
'引用DLL\DevExpress\DevExpress.XtraTreeList.v15.2.dll'
)
foreach ($relative in $dependencyFiles) {
if (-not (Test-RegularFile (Join-Path $Root $relative) 256MB)) {
throw 'legacy_reference_dependency_missing'
}
}
if ((Get-FileHash -LiteralPath (Join-Path $Root '引用DLL\Xilium.CefGlue.dll') `
-Algorithm SHA256).Hash.ToLowerInvariant() -ne $CefGlueSha256) {
throw 'cef_glue_binding_hash_mismatch'
}
return [ordered]@{
repositoryContractValid = $true
mainAssembly = $main.assemblyName
cliAssembly = $cli.assemblyName
targetFramework = 'v4.0'
platform = 'x86'
solutionConfiguration = 'Release|Mixed Platforms'
devExpressContract = '15.2'
cefRedistContract = [ordered]@{
packageId = $CefRedistPackageId
version = $CefRedistPackageVersion
sha256 = $CefRedistPackageSha256
}
projects = $paths
}
}
function Resolve-CefRedistPackage([string]$ExplicitPath) {
$candidates = New-Object System.Collections.Generic.List[string]
if (-not [string]::IsNullOrWhiteSpace($ExplicitPath)) {
$candidates.Add([IO.Path]::GetFullPath($ExplicitPath))
}
if (-not [string]::IsNullOrWhiteSpace($env:NUGET_PACKAGES)) {
$candidates.Add((Join-Path $env:NUGET_PACKAGES ("{0}\{1}\{0}.{1}.nupkg" -f `
$CefRedistPackageId, $CefRedistPackageVersion)))
}
$userProfile = [Environment]::GetFolderPath([Environment+SpecialFolder]::UserProfile)
if (-not [string]::IsNullOrWhiteSpace($userProfile)) {
$candidates.Add((Join-Path $userProfile (".nuget\packages\{0}\{1}\{0}.{1}.nupkg" -f `
$CefRedistPackageId, $CefRedistPackageVersion)))
}
foreach ($candidate in $candidates) {
if (-not (Test-RegularFile $candidate 256MB)) { continue }
$hash = (Get-FileHash -LiteralPath $candidate -Algorithm SHA256).Hash.ToLowerInvariant()
if ($hash -ne $CefRedistPackageSha256) { throw 'cef_redist_package_hash_mismatch' }
return $candidate
}
throw 'cef_redist_package_required'
}
function Expand-PinnedNuGetPackage([string]$PackagePath, [string]$Destination) {
Add-Type -AssemblyName System.IO.Compression.FileSystem
if ([IO.Directory]::Exists($Destination) -or [IO.File]::Exists($Destination)) {
throw 'cef_redist_destination_must_be_new'
}
[IO.Directory]::CreateDirectory($Destination) | Out-Null
$destinationRoot = [IO.Path]::GetFullPath($Destination).TrimEnd(
[char[]]@('\', '/')) + [IO.Path]::DirectorySeparatorChar
$archive = [IO.Compression.ZipFile]::OpenRead($PackagePath)
try {
if ($archive.Entries.Count -le 0 -or $archive.Entries.Count -gt 2000) {
throw 'cef_redist_archive_entry_count_invalid'
}
[long]$expandedBytes = 0
foreach ($entry in $archive.Entries) {
$relative = $entry.FullName.Replace('\', '/')
if ([string]::IsNullOrWhiteSpace($relative) -or
$relative.StartsWith('/') -or $relative.Contains(':') -or
$relative.Split('/') -contains '..') {
throw 'cef_redist_archive_path_invalid'
}
$expandedBytes += [long]$entry.Length
if ($entry.Length -lt 0 -or $entry.Length -gt 512MB -or
$expandedBytes -gt 1GB) {
throw 'cef_redist_archive_size_invalid'
}
$full = [IO.Path]::GetFullPath((Join-Path $Destination `
($relative.Replace('/', [string][IO.Path]::DirectorySeparatorChar))))
if (-not $full.StartsWith($destinationRoot, [StringComparison]::OrdinalIgnoreCase)) {
throw 'cef_redist_archive_path_invalid'
}
if ([string]::IsNullOrEmpty($entry.Name)) {
[IO.Directory]::CreateDirectory($full) | Out-Null
continue
}
[IO.Directory]::CreateDirectory([IO.Path]::GetDirectoryName($full)) | Out-Null
$input = $entry.Open()
$output = [IO.File]::Open(
$full, [IO.FileMode]::CreateNew, [IO.FileAccess]::Write, [IO.FileShare]::None)
try { $input.CopyTo($output); $output.Flush() }
finally { $output.Dispose(); $input.Dispose() }
}
}
finally { $archive.Dispose() }
if (-not (Test-RegularFile (Join-Path $Destination `
'build\cef.redist.x86.props') 4MB)) {
throw 'cef_redist_props_missing'
}
}
function Copy-CefRuntime([string]$ExpandedPackage, [string]$RuntimeDirectory) {
$source = Join-Path $ExpandedPackage 'CEF'
if (-not [IO.Directory]::Exists($source)) { throw 'cef_runtime_source_missing' }
$sourceItem = Get-Item -LiteralPath $source -Force
if (($sourceItem.Attributes -band [IO.FileAttributes]::ReparsePoint) -ne 0) {
throw 'cef_runtime_source_reparse_forbidden'
}
$sourceRoot = [IO.Path]::GetFullPath($source).TrimEnd([char[]]@('\', '/')) +
[IO.Path]::DirectorySeparatorChar
$runtimeRoot = [IO.Path]::GetFullPath($RuntimeDirectory).TrimEnd(
[char[]]@('\', '/')) + [IO.Path]::DirectorySeparatorChar
$files = @(Get-ChildItem -LiteralPath $source -Recurse -File -Force |
Sort-Object FullName)
if ($files.Count -le 0 -or $files.Count -gt 500) {
throw 'cef_runtime_file_count_invalid'
}
$relativePaths = New-Object `
'System.Collections.Generic.HashSet[string]' ([StringComparer]::OrdinalIgnoreCase)
[long]$totalBytes = 0
foreach ($file in $files) {
if (($file.Attributes -band [IO.FileAttributes]::ReparsePoint) -ne 0 -or
$file.Length -le 0 -or $file.Length -gt 512MB) {
throw 'cef_runtime_file_invalid'
}
$totalBytes += [long]$file.Length
if ($totalBytes -gt 1GB) { throw 'cef_runtime_total_size_invalid' }
$relative = (Get-RelativePath $source $file.FullName).Replace('\', '/')
if ([string]::IsNullOrWhiteSpace($relative) -or $relative.StartsWith('/') -or
$relative.Contains(':') -or $relative.Split('/') -contains '..' -or
-not $relativePaths.Add($relative)) {
throw 'cef_runtime_path_invalid'
}
$destination = [IO.Path]::GetFullPath((Join-Path $RuntimeDirectory `
($relative.Replace('/', [string][IO.Path]::DirectorySeparatorChar))))
if (-not $destination.StartsWith(
$runtimeRoot, [StringComparison]::OrdinalIgnoreCase)) {
throw 'cef_runtime_path_invalid'
}
[IO.Directory]::CreateDirectory([IO.Path]::GetDirectoryName($destination)) |
Out-Null
if ([IO.File]::Exists($destination)) {
if (-not (Test-RegularFile $destination 512MB) -or
(Get-FileHash -LiteralPath $destination -Algorithm SHA256).Hash -ne
(Get-FileHash -LiteralPath $file.FullName -Algorithm SHA256).Hash) {
throw 'cef_runtime_conflict'
}
}
else {
[IO.File]::Copy($file.FullName, $destination, $false)
}
}
$requiredHashes = [ordered]@{
'libcef.dll' = $CefLibcefSha256
'chrome_elf.dll' = 'b6194e1b093a000a7b9a883edfd4114a9e79332775970a20a36da1dd1837e8f3'
'icudtl.dat' = '8364e6c6bf5744357199de0de3f6ba30846ccda70288675b75059e6fd52241f3'
'locales/zh-CN.pak' = '6be9ef1c87b3162253090c00487238bbf4e5466d235cb49fdb69915a03c480cf'
'locales/en-US.pak' = 'f9e993df87cad724a36be1efb4f5a71322c9de4d0885419e5f13ca564115dce7'
}
foreach ($relative in $requiredHashes.Keys) {
$destination = Join-Path $RuntimeDirectory `
($relative.Replace('/', [string][IO.Path]::DirectorySeparatorChar))
if (-not (Test-RegularFile $destination 512MB) -or
(Get-FileHash -LiteralPath $destination -Algorithm SHA256).Hash.ToLowerInvariant() -ne
$requiredHashes[$relative]) {
throw 'cef_runtime_required_file_invalid'
}
}
}
function Assert-CefBindingVersion([string]$RuntimeDirectory) {
$bindingPath = Join-Path $RuntimeDirectory 'Xilium.CefGlue.dll'
$nativePath = Join-Path $RuntimeDirectory 'libcef.dll'
if (-not (Test-RegularFile $bindingPath 64MB) -or
-not (Test-RegularFile $nativePath 512MB)) {
throw 'cef_runtime_binding_missing'
}
if ((Get-FileHash -LiteralPath $bindingPath -Algorithm SHA256).Hash.ToLowerInvariant() -ne
$CefGlueSha256) {
throw 'cef_runtime_binding_hash_mismatch'
}
if ((Get-FileHash -LiteralPath $nativePath -Algorithm SHA256).Hash.ToLowerInvariant() -ne
$CefLibcefSha256) {
throw 'cef_runtime_native_hash_mismatch'
}
$bindingInfo = [Diagnostics.FileVersionInfo]::GetVersionInfo($bindingPath)
$nativeInfo = [Diagnostics.FileVersionInfo]::GetVersionInfo($nativePath)
$bindingVersion = [string]$bindingInfo.ProductVersion
$nativeVersion = [string]$nativeInfo.ProductVersion
if ([string]::IsNullOrWhiteSpace($nativeVersion)) {
$nativeVersion = [string]$nativeInfo.FileVersion
}
if (-not $bindingVersion.StartsWith('87.1.1', [StringComparison]::Ordinal) -or
(-not [string]::IsNullOrWhiteSpace($nativeVersion) -and
-not $nativeVersion.StartsWith('87.', [StringComparison]::Ordinal))) {
throw 'cef_runtime_binding_version_mismatch'
}
}
function Resolve-Tool([string]$ExplicitPath, [string]$ExpectedName) {
if ([string]::IsNullOrWhiteSpace($ExplicitPath)) { return $null }
$full = [IO.Path]::GetFullPath($ExplicitPath)
if ([IO.Path]::GetFileName($full) -ne $ExpectedName -or
-not (Test-RegularFile $full 128MB)) {
throw ("{0}_invalid" -f $ExpectedName.ToLowerInvariant())
}
return $full
}
function Find-MSBuild([string]$ExplicitPath) {
$resolved = Resolve-Tool $ExplicitPath 'MSBuild.exe'
if ($null -ne $resolved) { return $resolved }
$command = Get-Command MSBuild.exe -ErrorAction SilentlyContinue
if ($null -ne $command -and (Test-RegularFile $command.Source 128MB)) {
return $command.Source
}
$programFilesX86 = [Environment]::GetFolderPath([Environment+SpecialFolder]::ProgramFilesX86)
$vswhere = Join-Path $programFilesX86 'Microsoft Visual Studio\Installer\vswhere.exe'
if (Test-RegularFile $vswhere 32MB) {
$found = @(& $vswhere -latest -products '*' -requires Microsoft.Component.MSBuild `
-find 'MSBuild\**\Bin\MSBuild.exe' 2>$null)
foreach ($candidate in $found) {
if (Test-RegularFile ([string]$candidate) 128MB) { return [string]$candidate }
}
}
$framework = Join-Path $env:WINDIR 'Microsoft.NET\Framework\v4.0.30319\MSBuild.exe'
if (Test-RegularFile $framework 128MB) { return $framework }
throw 'msbuild_not_found'
}
function Find-EditBin([string]$ExplicitPath) {
$resolved = Resolve-Tool $ExplicitPath 'editbin.exe'
if ($null -ne $resolved) { return $resolved }
$command = Get-Command editbin.exe -ErrorAction SilentlyContinue
if ($null -ne $command -and (Test-RegularFile $command.Source 128MB)) {
return $command.Source
}
$programFilesX86 = [Environment]::GetFolderPath([Environment+SpecialFolder]::ProgramFilesX86)
$vswhere = Join-Path $programFilesX86 'Microsoft Visual Studio\Installer\vswhere.exe'
if (Test-RegularFile $vswhere 32MB) {
$installations = @(& $vswhere -products '*' `
-requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 `
-property installationPath 2>$null)
foreach ($installation in $installations) {
$toolRoot = Join-Path ([string]$installation) 'VC\Tools\MSVC'
if (-not [IO.Directory]::Exists($toolRoot)) { continue }
$candidates = @(Get-ChildItem -LiteralPath $toolRoot -Directory -Force |
Sort-Object Name -Descending | ForEach-Object {
Join-Path $_.FullName 'bin\Hostx64\x86\editbin.exe'
Join-Path $_.FullName 'bin\Hostx64\x64\editbin.exe'
})
foreach ($candidate in $candidates) {
if (Test-RegularFile $candidate 128MB) { return $candidate }
}
}
}
throw 'editbin_not_found'
}
function Find-SignTool([string]$ExplicitPath) {
$resolved = Resolve-Tool $ExplicitPath 'signtool.exe'
if ($null -ne $resolved) { return $resolved }
$command = Get-Command signtool.exe -ErrorAction SilentlyContinue
if ($null -ne $command -and (Test-RegularFile $command.Source 128MB)) {
return $command.Source
}
$programFilesX86 = [Environment]::GetFolderPath([Environment+SpecialFolder]::ProgramFilesX86)
foreach ($kitsVersion in @('10', '8.1')) {
$binRoot = Join-Path $programFilesX86 ("Windows Kits\{0}\bin" -f $kitsVersion)
if (-not [IO.Directory]::Exists($binRoot)) { continue }
$candidates = @(Get-ChildItem -LiteralPath $binRoot -Directory -Force |
Sort-Object Name -Descending | ForEach-Object {
Join-Path $_.FullName 'x86\signtool.exe'
Join-Path $_.FullName 'x64\signtool.exe'
})
$candidates += @(Join-Path $binRoot 'x86\signtool.exe')
foreach ($candidate in $candidates) {
if (Test-RegularFile $candidate 128MB) { return $candidate }
}
}
throw 'signtool_not_found'
}
function Get-SigningCertificate(
[string]$Thumbprint,
[string]$StoreLocation) {
$normalized = $Thumbprint.Replace(' ', '').ToUpperInvariant()
$path = "Cert:\{0}\My\{1}" -f $StoreLocation, $normalized
if (-not (Test-Path -LiteralPath $path)) { throw 'signing_certificate_missing' }
$certificate = Get-Item -LiteralPath $path
$codeSigningOid = '1.3.6.1.5.5.7.3.3'
$hasCodeSigningEku = @($certificate.EnhancedKeyUsageList | Where-Object {
$_.ObjectId.Value -eq $codeSigningOid
}).Count -gt 0
$now = [DateTime]::UtcNow
if (-not $certificate.HasPrivateKey -or -not $hasCodeSigningEku -or
$certificate.NotBefore.ToUniversalTime() -gt $now -or
$certificate.NotAfter.ToUniversalTime() -le $now) {
throw 'signing_certificate_invalid'
}
return $certificate
}
function Read-U16([byte[]]$Bytes, [int]$Offset) {
if ($Offset -lt 0 -or $Offset + 2 -gt $Bytes.Length) { throw 'pe_offset_invalid' }
return [BitConverter]::ToUInt16($Bytes, $Offset)
}
function Read-U32([byte[]]$Bytes, [int]$Offset) {
if ($Offset -lt 0 -or $Offset + 4 -gt $Bytes.Length) { throw 'pe_offset_invalid' }
return [BitConverter]::ToUInt32($Bytes, $Offset)
}
function Convert-RvaToFileOffset([byte[]]$Bytes, [int]$PeOffset, [uint32]$Rva) {
$sectionCount = Read-U16 $Bytes ($PeOffset + 6)
$optionalSize = Read-U16 $Bytes ($PeOffset + 20)
$sectionTable = $PeOffset + 24 + $optionalSize
for ($index = 0; $index -lt $sectionCount; $index++) {
$section = $sectionTable + ($index * 40)
$virtualSize = Read-U32 $Bytes ($section + 8)
$virtualAddress = Read-U32 $Bytes ($section + 12)
$rawSize = Read-U32 $Bytes ($section + 16)
$rawPointer = Read-U32 $Bytes ($section + 20)
$span = [Math]::Max([long]$virtualSize, [long]$rawSize)
if ([long]$Rva -ge [long]$virtualAddress -and
[long]$Rva -lt ([long]$virtualAddress + $span)) {
$offset = [long]$rawPointer + ([long]$Rva - [long]$virtualAddress)
if ($offset -lt 0 -or $offset -gt [int]::MaxValue -or $offset -ge $Bytes.Length) {
throw 'pe_rva_invalid'
}
return [int]$offset
}
}
throw 'pe_rva_unmapped'
}
function Get-ManagedPeInfo([string]$Path) {
if (-not (Test-RegularFile $Path 512MB)) { throw 'managed_binary_invalid' }
[byte[]]$bytes = [IO.File]::ReadAllBytes($Path)
if ((Read-U16 $bytes 0) -ne 0x5A4D) { throw 'pe_dos_signature_invalid' }
$peOffset = [int](Read-U32 $bytes 0x3C)
if ((Read-U32 $bytes $peOffset) -ne 0x00004550) { throw 'pe_signature_invalid' }
$machine = Read-U16 $bytes ($peOffset + 4)
$characteristics = Read-U16 $bytes ($peOffset + 22)
$optional = $peOffset + 24
if ((Read-U16 $bytes $optional) -ne 0x010B) { throw 'pe32_required' }
if ((Read-U32 $bytes ($optional + 92)) -lt 15) { throw 'cli_directory_missing' }
$cliRva = Read-U32 $bytes ($optional + 96 + (14 * 8))
if ($cliRva -eq 0) { throw 'cli_header_missing' }
$cliOffset = Convert-RvaToFileOffset $bytes $peOffset $cliRva
$metadataRva = Read-U32 $bytes ($cliOffset + 8)
$corFlags = Read-U32 $bytes ($cliOffset + 16)
$metadataOffset = Convert-RvaToFileOffset $bytes $peOffset $metadataRva
if ((Read-U32 $bytes $metadataOffset) -ne 0x424A5342) {
throw 'cli_metadata_signature_invalid'
}
$versionLength = [int](Read-U32 $bytes ($metadataOffset + 12))
if ($versionLength -le 0 -or $versionLength -gt 128 -or
$metadataOffset + 16 + $versionLength -gt $bytes.Length) {
throw 'cli_metadata_version_invalid'
}
$runtimeVersion = [Text.Encoding]::ASCII.GetString(
$bytes, $metadataOffset + 16, $versionLength).Trim([char]0).Trim()
$assemblyName = [Reflection.AssemblyName]::GetAssemblyName($Path).Name
return [ordered]@{
assemblyName = $assemblyName
machine = ("0x{0:x4}" -f $machine)
pe32 = $true
ilOnly = (($corFlags -band 0x1) -ne 0)
bit32Required = (($corFlags -band 0x2) -ne 0)
largeAddressAware = (($characteristics -band 0x20) -ne 0)
corFlags = ("0x{0:x8}" -f $corFlags)
runtimeVersion = $runtimeVersion
}
}
function Write-NewUtf8Json([string]$Path, [object]$Value) {
$json = ($Value | ConvertTo-Json -Depth 12)
$encoding = New-Object Text.UTF8Encoding($false, $true)
[byte[]]$body = $encoding.GetBytes($json + [Environment]::NewLine)
$stream = [IO.File]::Open(
$Path, [IO.FileMode]::CreateNew, [IO.FileAccess]::Write, [IO.FileShare]::None)
try {
$stream.Write($body, 0, $body.Length)
$stream.Flush()
}
finally { $stream.Dispose() }
}
$root = [IO.Path]::GetFullPath($RepoRoot).TrimEnd([char[]]@('\', '/'))
if (-not [IO.Directory]::Exists($root)) { throw 'repository_root_missing' }
$rootItem = Get-Item -LiteralPath $root -Force
if (($rootItem.Attributes -band [IO.FileAttributes]::ReparsePoint) -ne 0) {
throw 'repository_root_reparse_forbidden'
}
$contract = Test-RepositoryContract $root
if ($PSCmdlet.ParameterSetName -eq 'Inspect') {
[ordered]@{
schemaVersion = '1.0'
repositoryContractValid = $true
contract = $contract
} | ConvertTo-Json -Depth 8
exit 0
}
if ($env:OS -ne 'Windows_NT') { throw 'windows_required' }
$target = [IO.Path]::GetFullPath($OutputDirectory).TrimEnd([char[]]@('\', '/'))
if (-not [IO.Path]::IsPathRooted($target) -or [IO.Directory]::Exists($target) -or
[IO.File]::Exists($target) -or $target -eq [IO.Path]::GetPathRoot($target) -or
$target -eq $root) {
throw 'output_directory_must_be_new_and_specific'
}
$parent = [IO.Path]::GetDirectoryName($target)
if ([string]::IsNullOrWhiteSpace($parent)) { throw 'output_parent_invalid' }
[IO.Directory]::CreateDirectory($parent) | Out-Null
$parentItem = Get-Item -LiteralPath $parent -Force
if (($parentItem.Attributes -band [IO.FileAttributes]::ReparsePoint) -ne 0) {
throw 'output_parent_reparse_forbidden'
}
$referenceAssemblies = Join-Path `
([Environment]::GetFolderPath([Environment+SpecialFolder]::ProgramFilesX86)) `
'Reference Assemblies\Microsoft\Framework\.NETFramework\v4.0\mscorlib.dll'
if (-not (Test-RegularFile $referenceAssemblies 64MB)) {
throw 'dotnet_framework_4_targeting_pack_missing'
}
$msbuild = Find-MSBuild $MSBuildPath
$editbin = Find-EditBin $EditBinPath
$cefRedistPackage = Resolve-CefRedistPackage $CefRedistPackagePath
$signingEnabled = -not [string]::IsNullOrWhiteSpace($AuthenticodeCertificateThumbprint)
$signTool = $null
$signingCertificate = $null
$timestampUri = $null
if ($signingEnabled) {
if (-not [Uri]::TryCreate($TimestampUrl, [UriKind]::Absolute, [ref]$timestampUri) -or
$timestampUri.Scheme -ne 'https') {
throw 'https_timestamp_url_required'
}
$signTool = Find-SignTool $SignToolPath
$signingCertificate = Get-SigningCertificate `
$AuthenticodeCertificateThumbprint $CertificateStoreLocation
}
elseif (-not [string]::IsNullOrWhiteSpace($SignToolPath) -or
-not [string]::IsNullOrWhiteSpace($TimestampUrl)) {
throw 'signing_parameters_incomplete'
}
$gitCommand = Get-Command git.exe -ErrorAction SilentlyContinue
if ($null -eq $gitCommand) { $gitCommand = Get-Command git -ErrorAction SilentlyContinue }
if ($null -eq $gitCommand) { throw 'git_not_found' }
$sourceCommit = ((& $gitCommand.Source -C $root rev-parse HEAD 2>$null) | Out-String).Trim()
if ($LASTEXITCODE -ne 0 -or $sourceCommit -notmatch '^[A-Fa-f0-9]{40}$') {
throw 'source_commit_unavailable'
}
if ($sourceCommit.ToLowerInvariant() -ne $ExpectedSourceCommit.ToLowerInvariant()) {
throw 'source_commit_mismatch'
}
$dirty = @(& $gitCommand.Source -C $root status --porcelain=v1 --untracked-files=all 2>$null)
if ($LASTEXITCODE -ne 0) { throw 'source_status_unavailable' }
if ($dirty.Count -ne 0) { throw 'source_worktree_must_be_clean' }
$temporary = Join-Path $parent ('.lserp-legacy-build-' + [Guid]::NewGuid().ToString('N'))
$sourceCheckout = Join-Path $parent ('.lserp-legacy-source-' + [Guid]::NewGuid().ToString('N'))
$runtime = Join-Path $temporary 'Runtime'
$logPath = Join-Path $temporary 'MSBUILD.log'
$evidencePath = Join-Path $temporary 'LEGACY-BUILD-EVIDENCE.json'
[IO.Directory]::CreateDirectory($runtime) | Out-Null
$completed = $false
try {
& $gitCommand.Source clone --quiet --no-hardlinks --no-checkout $root $sourceCheckout
if ($LASTEXITCODE -ne 0 -or -not [IO.Directory]::Exists($sourceCheckout)) {
throw 'source_checkout_clone_failed'
}
& $gitCommand.Source -C $sourceCheckout checkout --quiet --detach $sourceCommit
if ($LASTEXITCODE -ne 0) { throw 'source_checkout_failed' }
$checkoutCommit = ((& $gitCommand.Source -C $sourceCheckout rev-parse HEAD 2>$null) |
Out-String).Trim()
if ($LASTEXITCODE -ne 0 -or
$checkoutCommit.ToLowerInvariant() -ne $sourceCommit.ToLowerInvariant()) {
throw 'source_checkout_commit_mismatch'
}
$buildContract = Test-RepositoryContract $sourceCheckout
$cefDestination = Join-Path $sourceCheckout ("插件库\Lskj.LserpAll\packages\{0}.{1}" -f `
$CefRedistPackageId, $CefRedistPackageVersion)
Expand-PinnedNuGetPackage $cefRedistPackage $cefDestination
$outDir = $runtime.TrimEnd([char[]]@('\', '/')) + [IO.Path]::DirectorySeparatorChar
$devExpressReferencePath = Join-Path $sourceCheckout '引用DLL\DevExpress'
$arguments = @(
$buildContract.projects.solution,
'/nologo',
'/m',
'/t:Lskj_Cli:Rebuild',
'/p:Configuration=Release',
'/p:Platform=Mixed Platforms',
('/p:OutDir=' + $outDir),
('/p:ReferencePath=' + $devExpressReferencePath),
('/p:LegacyEditBinPath=' + $editbin),
'/p:DebugSymbols=false',
'/p:DebugType=None'
)
& $msbuild @arguments 2>&1 | Tee-Object -LiteralPath $logPath
$buildExitCode = $LASTEXITCODE
if ($buildExitCode -ne 0) { throw 'legacy_msbuild_failed' }
Copy-CefRuntime $cefDestination $runtime
$required = [ordered]@{
erp = 'Ls_ERP.exe'
cli = 'lserp-cli.exe'
bridge = 'Lskj.AgentBridge.dll'
kernel = 'Lskj.CommandKernel.dll'
core = 'Lskj.Core.dll'
json = 'Newtonsoft.Json.dll'
cefGlue = 'Xilium.CefGlue.dll'
cefNative = 'libcef.dll'
cefChromeElf = 'chrome_elf.dll'
cefIcu = 'icudtl.dat'
cefZhCn = 'locales\zh-CN.pak'
cefEnUs = 'locales\en-US.pak'
devexpressData = 'DevExpress.Data.v15.2.dll'
devexpressEditors = 'DevExpress.XtraEditors.v15.2.dll'
devexpressGrid = 'DevExpress.XtraGrid.v15.2.dll'
}
foreach ($name in $required.Values) {
if (-not (Test-RegularFile (Join-Path $runtime $name) 512MB)) {
throw ("required_build_artifact_missing:{0}" -f $name)
}
}
Assert-CefBindingVersion $runtime
$signedRelativeFiles = @(
'Ls_ERP.exe',
'lserp-cli.exe',
'Lskj.AgentBridge.dll',
'Lskj.CommandKernel.dll',
'Lskj.Core.dll'
)
Assert-NoHardcodedSqlCredentials $runtime
if ($signingEnabled) {
foreach ($relative in $signedRelativeFiles) {
$fileToSign = Join-Path $runtime $relative
$signArguments = @(
'sign', '/nologo', '/sha1', $signingCertificate.Thumbprint,
'/s', 'My', '/fd', 'SHA256', '/tr', $timestampUri.AbsoluteUri,
'/td', 'SHA256', $fileToSign
)
if ($CertificateStoreLocation -eq 'LocalMachine') {
$signArguments = @('sign', '/nologo', '/sm', '/sha1',
$signingCertificate.Thumbprint, '/s', 'My', '/fd', 'SHA256',
'/tr', $timestampUri.AbsoluteUri, '/td', 'SHA256', $fileToSign)
}
& $signTool @signArguments 2>&1 | Tee-Object -LiteralPath $logPath -Append
if ($LASTEXITCODE -ne 0) { throw 'legacy_authenticode_signing_failed' }
$signature = Get-AuthenticodeSignature -LiteralPath $fileToSign
if ($signature.Status -ne [System.Management.Automation.SignatureStatus]::Valid -or
$null -eq $signature.SignerCertificate -or
$signature.SignerCertificate.Thumbprint -ne $signingCertificate.Thumbprint) {
throw 'legacy_authenticode_verification_failed'
}
}
}
$erpInfo = Get-ManagedPeInfo (Join-Path $runtime $required.erp)
$cliInfo = Get-ManagedPeInfo (Join-Path $runtime $required.cli)
if ($erpInfo.assemblyName -ne 'Ls_ERP' -or $cliInfo.assemblyName -ne 'lserp-cli' -or
$erpInfo.machine -ne '0x014c' -or $cliInfo.machine -ne '0x014c' -or
-not $erpInfo.pe32 -or -not $cliInfo.pe32 -or
-not $erpInfo.ilOnly -or -not $cliInfo.ilOnly -or
-not $erpInfo.bit32Required -or -not $cliInfo.bit32Required -or
-not $erpInfo.largeAddressAware -or
-not $erpInfo.runtimeVersion.StartsWith('v4.0', [StringComparison]::Ordinal) -or
-not $cliInfo.runtimeVersion.StartsWith('v4.0', [StringComparison]::Ordinal)) {
throw 'legacy_binary_contract_invalid'
}
$sourceEvidencePaths = @(
'插件库\Lskj.Main\Lskj.Main.csproj',
'插件库\Lskj.Main\FrmMain.cs',
'插件库\Lskj.Main\Hosting\ErpAgentBridgeBootstrap.cs',
'插件库\Lskj.Main\Hosting\ModuleDiagnosticCommandHandlers.cs',
'插件库\Lskj.Main\Hosting\BusinessWorkflowRegistration.cs',
'插件库\Lskj.Main\Hosting\SqlWorkflowProcedureGateway.cs',
'插件库\Lskj.Main\Hosting\SqlDynamicModuleWriteAdapter.cs',
'插件库\Lskj.Main\Hosting\DynamicModuleWriteAvailability.cs',
'插件库\Lskj.Main\Hosting\SqlDynamicModuleUpdateAdapter.cs',
'插件库\Lskj.Main\Hosting\DynamicModuleUpdateAvailability.cs',
'插件库\Lskj.Main\Hosting\DynamicModuleUpdateCommandHandlers.cs',
'插件库\Lskj.Cli\Lskj.Cli.csproj',
'插件库\Lskj.Cli\CliApplication.cs',
'插件库\Lskj.Cli\BridgeCliClient.cs',
'插件库\Lskj.Cli\BridgeCommands.cs',
'插件库\Lskj.Cli\WorkflowCommands.cs',
'插件库\Lskj.Cli\ModuleInspector.cs',
'插件库\Lskj.Control\Lskj.Control.csproj',
'插件库\Lskj.AgentBridge\AgentBridgeRuntime.cs',
'插件库\Lskj.AgentBridge\BusinessAcceptanceEvidence.cs',
'插件库\Lskj.AgentBridge\WorkflowWriteIntegrationEvidence.cs',
'插件库\Lskj.AgentBridge\CustomerAcceptanceBundleEvidence.cs',
'插件库\Lskj.AgentBridge\DynamicModuleWriteAcceptance.cs',
'插件库\Lskj.AgentBridge\DynamicModuleUpdateAcceptance.cs',
'插件库\Lskj.Data\Lskj.Data.csproj',
'插件库\Lskj.LegacyApiCompatibility.Tests\Lskj.LegacyApiCompatibility.Tests.csproj',
'插件库\Lskj.CommandKernel\CommandDispatcher.cs',
'插件库\Lskj.CommandKernel\ModuleMenuDiscovery.cs',
'插件库\Lskj.CommandKernel\DynamicModuleOperations.cs',
'插件库\Lskj.CommandKernel\DynamicModuleLookupResolution.cs',
'插件库\Lskj.CommandKernel\DynamicModuleNativeExecution.cs',
'插件库\Lskj.CommandKernel\DynamicModuleWrites.cs',
'插件库\Lskj.CommandKernel\DynamicModuleUpdates.cs'
)
$sourceFiles = @($sourceEvidencePaths | ForEach-Object {
$full = Join-Path $sourceCheckout $_
if (-not (Test-RegularFile $full 8MB)) { throw 'source_evidence_file_missing' }
[ordered]@{
path = $_.Replace('\', '/')
sha256 = (Get-FileHash -LiteralPath $full -Algorithm SHA256).Hash.ToLowerInvariant()
}
})
$files = @()
foreach ($file in @(Get-ChildItem -LiteralPath $temporary -Recurse -File -Force |
Where-Object { $_.FullName -ne $evidencePath } |
Sort-Object FullName)) {
if (($file.Attributes -band [IO.FileAttributes]::ReparsePoint) -ne 0 -or
$file.Length -le 0 -or $file.Length -gt 512MB) {
throw 'build_artifact_file_invalid'
}
$files += [ordered]@{
path = (Get-RelativePath $temporary $file.FullName)
sizeBytes = $file.Length
sha256 = (Get-FileHash -LiteralPath $file.FullName -Algorithm SHA256).Hash.ToLowerInvariant()
}
}
if ($files.Count -le 0 -or $files.Count -gt 2000) { throw 'build_artifact_count_invalid' }
Remove-Item -LiteralPath $sourceCheckout -Recurse -Force
if ([IO.Directory]::Exists($sourceCheckout)) { throw 'source_checkout_cleanup_failed' }
$sourceCheckout = ''
$remainingHardGates = @(
'customer Windows ERP startup and named-pipe ACL integration',
'customer SQL Server transaction, rollback, idempotency and audit evidence',
'signed workflow acceptance manifests bound to final runtime configuration'
)
if (-not $signingEnabled) {
$remainingHardGates = @(
'valid Authenticode signatures for final legacy binaries and installer'
) + $remainingHardGates
}
$report = [ordered]@{
schemaVersion = '1.0'
generatedAtUtc = [DateTime]::UtcNow.ToString('o')
buildVerified = $true
releaseReadiness = $false
sourceCommit = $sourceCommit.ToLowerInvariant()
sourceWorktreeDirty = $false
configuration = 'Release'
platform = 'x86'
targetFramework = 'v4.0'
devExpressContract = '15.2'
dependencies = [ordered]@{
cefRedistX86 = [ordered]@{
packageId = $CefRedistPackageId
version = $CefRedistPackageVersion
sha256 = $CefRedistPackageSha256
}
}
msbuildVersion = ([Diagnostics.FileVersionInfo]::GetVersionInfo($msbuild).FileVersion)
editbinVersion = ([Diagnostics.FileVersionInfo]::GetVersionInfo($editbin).FileVersion)
authenticode = [ordered]@{
signed = $signingEnabled
certificateThumbprint = if ($signingEnabled) {
$signingCertificate.Thumbprint.ToUpperInvariant()
} else { $null }
certificateStoreLocation = if ($signingEnabled) {
$CertificateStoreLocation
} else { $null }
timestampUrl = if ($signingEnabled) { $timestampUri.AbsoluteUri } else { $null }
files = if ($signingEnabled) { $signedRelativeFiles } else { @() }
}
erpBinary = $erpInfo
cliBinary = $cliInfo
sourceFiles = $sourceFiles
files = $files
remainingHardGates = $remainingHardGates
}
Write-NewUtf8Json $evidencePath $report
[IO.Directory]::Move($temporary, $target)
$completed = $true
[ordered]@{
buildVerified = $true
releaseReadiness = $false
outputDirectory = $target
evidencePath = (Join-Path $target 'LEGACY-BUILD-EVIDENCE.json')
sourceCommit = $sourceCommit.ToLowerInvariant()
} | ConvertTo-Json -Depth 4
}
finally {
if (-not [string]::IsNullOrWhiteSpace($sourceCheckout) -and
[IO.Directory]::Exists($sourceCheckout)) {
Remove-Item -LiteralPath $sourceCheckout -Recurse -Force
}
if (-not $completed -and [IO.Directory]::Exists($temporary)) {
Remove-Item -LiteralPath $temporary -Recurse -Force
}
}