param( [string]$DeployedRoot = '\\phpsrv03\NBIM\save\Apps\Plant3D\DriveA\P_Rec_Annot', [string]$SourceRoot = '\\phpsrv03\NBIM\code\CS\P_Rec_Annot\v1.0.4\repo', [string]$OutputRoot = (Split-Path -Parent $PSCommandPath) ) Set-StrictMode -Version Latest $ErrorActionPreference = 'Stop' function Get-PeArchitecture([string]$Path) { if ([IO.Path]::GetExtension($Path) -notin '.dll', '.exe') { return $null } try { $stream = [IO.File]::Open($Path, [IO.FileMode]::Open, [IO.FileAccess]::Read, [IO.FileShare]::ReadWrite) try { $reader = [IO.BinaryReader]::new($stream) $stream.Position = 0x3c; $offset = $reader.ReadInt32(); $stream.Position = $offset + 4 $machine = $reader.ReadUInt16() switch ($machine) { 0x14c { 'x86' } 0x8664 { 'x64' } 0x1c4 { 'ARM' } default { 'Unknown' } } } finally { $stream.Dispose() } } catch { 'Unreadable' } } function Get-AssemblyMetadata([string]$Path) { $result = [ordered]@{ AssemblyName = $null; AssemblyVersion = $null; TargetFramework = $null } if ([IO.Path]::GetExtension($Path) -ne '.dll') { return [pscustomobject]$result } try { $name = [Reflection.AssemblyName]::GetAssemblyName($Path) $result.AssemblyName = $name.Name; $result.AssemblyVersion = $name.Version.ToString() try { $assembly = [Reflection.Assembly]::ReflectionOnlyLoadFrom($Path) $attribute = $assembly.GetCustomAttributesData() | Where-Object { $_.AttributeType.FullName -eq 'System.Runtime.Versioning.TargetFrameworkAttribute' } | Select-Object -First 1 if ($attribute) { $result.TargetFramework = [string]$attribute.ConstructorArguments[0].Value } } catch { } } catch { } [pscustomobject]$result } $inventory = foreach ($file in Get-ChildItem -LiteralPath $DeployedRoot -Recurse -File) { $hash = Get-FileHash -LiteralPath $file.FullName -Algorithm SHA256 $version = try { [Diagnostics.FileVersionInfo]::GetVersionInfo($file.FullName) } catch { $null } $meta = Get-AssemblyMetadata $file.FullName [pscustomobject]@{ RelativePath = $file.FullName.Substring($DeployedRoot.Length).TrimStart('\') Name = $file.Name; Extension = $file.Extension; Length = $file.Length; LastWriteTimeUtc = $file.LastWriteTimeUtc.ToString('o') Sha256 = $hash.Hash; FileVersion = $version.FileVersion; ProductVersion = $version.ProductVersion AssemblyName = $meta.AssemblyName; AssemblyVersion = $meta.AssemblyVersion; TargetFramework = $meta.TargetFramework PeArchitecture = Get-PeArchitecture $file.FullName } } $inventory | Sort-Object RelativePath | Export-Csv -LiteralPath (Join-Path $OutputRoot 'deployed-files.csv') -NoTypeInformation -Encoding utf8 $settings = [xml](Get-Content -LiteralPath (Join-Path $DeployedRoot 'Settings.xml')) $sequence = 0 $configuration = foreach ($item in $settings.Main.LoadAssemblies.LoadAssembly) { $sequence++ $configured = [string]$item.assemblyname; $resolved = Join-Path $DeployedRoot $configured $entry = $inventory | Where-Object Name -eq $configured | Select-Object -First 1 [pscustomobject]@{ Sequence = $sequence; ConfigFile = 'Settings.xml'; ConfiguredPath = $configured; ResolvedPath = ('\' + $configured) Assembly = $entry.AssemblyName; Exists = Test-Path -LiteralPath $resolved; Sha256 = $entry.Sha256 LoadMethod = 'acDoc.Editor.Command("._netload", fullPath)'; Required = $true FailureBehavior = 'Outer loader catch stops remaining sequence and shows MessageBox'; Notes = 'Path is combined with OrthoLoader assembly directory.' } } $configuration | Export-Csv -LiteralPath (Join-Path $OutputRoot 'configuration-map.csv') -NoTypeInformation -Encoding utf8 $projects = Get-ChildItem -LiteralPath $SourceRoot -Recurse -File -Filter *.csproj | ForEach-Object { $content = Get-Content -LiteralPath $_.FullName -Raw $assemblyName = if ($content -match '([^<]+)') { $Matches[1] } else { [IO.Path]::GetFileNameWithoutExtension($_.Name) } $framework = if ($content -match '([^<]+)') { $Matches[1] } else { $null } $platform = if ($content -match '([^<]+)') { $Matches[1] } else { $null } [pscustomobject]@{ AssemblyName = $assemblyName; Csproj = $_.FullName.Substring($SourceRoot.Length).TrimStart('\'); TargetFramework = $framework; PlatformTarget = $platform } } $sourceMap = foreach ($dll in $inventory | Where-Object Extension -eq '.dll') { $matches = @($projects | Where-Object { $_.AssemblyName -eq $dll.AssemblyName -or $_.AssemblyName -eq [IO.Path]::GetFileNameWithoutExtension($dll.Name) }) $releaseCandidate = Join-Path (Join-Path $SourceRoot 'FilesRelease') $dll.Name $exactReleaseMatch = (Test-Path -LiteralPath $releaseCandidate) -and ((Get-FileHash -LiteralPath $releaseCandidate -Algorithm SHA256).Hash -eq $dll.Sha256) $status = if ($exactReleaseMatch) { 'EXACT_SOURCE_MATCH' } elseif ($matches.Count -eq 1) { 'LIKELY_SOURCE_MATCH' } elseif ($matches.Count -gt 1) { 'MULTIPLE_CANDIDATES' } elseif ($dll.Name -match '^PnP|^Ac') { 'AUTODESK_DEPENDENCY' } else { 'NO_SOURCE_FOUND' } $project = $matches | Select-Object -First 1 [pscustomobject]@{ DeployedDll=$dll.Name; DeployedSha256=$dll.Sha256; FileVersion=$dll.FileVersion; AssemblyVersion=$dll.AssemblyVersion; LegacyProject=if($project){[IO.Path]::GetFileNameWithoutExtension($project.Csproj)}else{$null}; LegacyCsproj=if($project){$project.Csproj}else{$null}; LegacyOutputName=if($project){$project.AssemblyName}else{$null}; TargetFramework=if($project){$project.TargetFramework}else{$null}; PlatformTarget=if($project){$project.PlatformTarget}else{$null}; CommandCount=$null; RibbonRole=if($dll.AssemblyName -eq 'OrthoLoader'){'Central ribbon/loader'}else{$null}; ConfigurationSource=if($configuration.ConfiguredPath -contains $dll.Name){'Settings.xml'}else{$null}; MatchStatus=$status } } $sourceMap | Sort-Object DeployedDll | Export-Csv -LiteralPath (Join-Path $OutputRoot 'deployed-to-source-map.csv') -NoTypeInformation -Encoding utf8 "GeneratedUtc=$([DateTime]::UtcNow.ToString('o'))`nDeployedRoot=`nSourceRoot=`nFiles=$($inventory.Count)" | Set-Content -LiteralPath (Join-Path $OutputRoot 'evidence\generation.txt') -Encoding utf8