#Requires -Version 5.1 <# .SYNOPSIS Copies build artifacts from an ephemeral VM to the host artifact directory. .DESCRIPTION Opens a WinRM session to the build VM and uses Copy-Item -FromSession to transfer the artifact archive and optional log files. Validates that at least one file was successfully transferred. Throws on failure so the caller's try/finally can clean up the VM. .PARAMETER IPAddress IP of the build VM. .PARAMETER Credential PSCredential for the guest build user. .PARAMETER GuestArtifactPath Full path to the artifact file or directory inside the VM. Example: C:\CI\output\artifacts.zip .PARAMETER HostArtifactDir Directory on the host where artifacts will be stored. The directory is created if it does not exist. Example: F:\CI\Artifacts\run-42 .PARAMETER IncludeLogs If set, also copies *.log files from the guest work directory (C:\CI\build\*.log) alongside the artifact archive. .EXAMPLE $cred = Get-Credential .\Get-BuildArtifacts.ps1 ` -IPAddress "192.168.11.101" ` -Credential $cred ` -GuestArtifactPath "C:\CI\output\artifacts.zip" ` -HostArtifactDir "F:\CI\Artifacts\run-42" #> [CmdletBinding()] param( [Parameter(Mandatory)] [ValidatePattern('^(\d{1,3}\.){3}\d{1,3}$')] [string] $IPAddress, [Parameter(Mandatory)] [System.Management.Automation.PSCredential] $Credential, [Parameter(Mandatory)] [string] $GuestArtifactPath, [Parameter(Mandatory)] [string] $HostArtifactDir, [switch] $IncludeLogs ) Set-StrictMode -Version Latest $ErrorActionPreference = 'Stop' # Ensure destination exists on host if (-not (Test-Path $HostArtifactDir)) { New-Item -ItemType Directory -Path $HostArtifactDir -Force | Out-Null Write-Host "[Get-BuildArtifacts] Created host artifact directory: $HostArtifactDir" } $sessionOptions = New-PSSessionOption -SkipCACheck -SkipCNCheck -SkipRevocationCheck Write-Host "[Get-BuildArtifacts] Connecting to VM at $IPAddress..." $session = New-PSSession ` -ComputerName $IPAddress ` -Credential $Credential ` -SessionOption $sessionOptions ` -ErrorAction Stop try { # ── Verify artifact exists in guest ────────────────────────────────── $guestExists = Invoke-Command -Session $session -ScriptBlock { param($path) Test-Path $path } -ArgumentList $GuestArtifactPath if (-not $guestExists) { throw "Artifact not found in guest at: $GuestArtifactPath" } # ── Copy main artifact ──────────────────────────────────────────────── $artifactFileName = Split-Path $GuestArtifactPath -Leaf $hostDestPath = Join-Path $HostArtifactDir $artifactFileName Write-Host "[Get-BuildArtifacts] Copying $artifactFileName from guest..." Copy-Item -Path $GuestArtifactPath -Destination $hostDestPath -FromSession $session -Force # ── Copy logs if requested ──────────────────────────────────────────── if ($IncludeLogs) { $logFiles = Invoke-Command -Session $session -ScriptBlock { Get-ChildItem -Path 'C:\CI\build' -Filter '*.log' -ErrorAction SilentlyContinue | Select-Object -ExpandProperty FullName } foreach ($log in $logFiles) { $logDest = Join-Path $HostArtifactDir (Split-Path $log -Leaf) Write-Host "[Get-BuildArtifacts] Copying log: $(Split-Path $log -Leaf)" Copy-Item -Path $log -Destination $logDest -FromSession $session -Force } } # ── Validate at least the main artifact landed ──────────────────────── if (-not (Test-Path $hostDestPath)) { throw "Artifact was not found at expected host path after copy: $hostDestPath" } $sizeKB = [math]::Round((Get-Item $hostDestPath).Length / 1KB, 1) if ($sizeKB -eq 0) { throw "Artifact file exists but is empty: $hostDestPath" } Write-Host "[Get-BuildArtifacts] Artifact collected: $hostDestPath ($sizeKB KB)" return $hostDestPath } finally { Remove-PSSession $session -ErrorAction SilentlyContinue }