functions/public/Send-KlippyGcodeFile.ps1
|
function Send-KlippyGcodeFile { <# .SYNOPSIS Uploads a G-code file to a Klipper printer. .DESCRIPTION Uploads a local G-code file to the printer's gcodes storage. Supports uploading to subdirectories and optional print-after-upload. .PARAMETER Id The unique identifier of the printer. .PARAMETER PrinterName The friendly name of the printer. .PARAMETER InputObject A printer object from pipeline input. .PARAMETER FilePath Local path to the G-code file to upload. .PARAMETER Destination Destination path on the printer (relative to gcodes root). If not specified, uploads to root with original filename. .PARAMETER StartPrint Start printing the file immediately after upload. .PARAMETER PassThru Return the uploaded file object. .EXAMPLE Send-KlippyGcodeFile -FilePath "C:\prints\benchy.gcode" Uploads a file to the default printer. .EXAMPLE Send-KlippyGcodeFile -PrinterName "voronv2" -FilePath "./model.gcode" -Destination "projects/" Uploads to a specific folder. .EXAMPLE Send-KlippyGcodeFile -FilePath "urgent.gcode" -StartPrint Uploads and immediately starts printing. .EXAMPLE Get-ChildItem *.gcode | ForEach-Object { Send-KlippyGcodeFile -FilePath $_.FullName -PassThru } Uploads multiple files. .OUTPUTS PSCustomObject with file information (when -PassThru is used). #> [CmdletBinding(DefaultParameterSetName = 'Default', SupportsShouldProcess = $true)] [OutputType([PSCustomObject])] param( [Parameter(Mandatory = $true, ParameterSetName = 'ById')] [ValidateNotNullOrEmpty()] [string]$Id, [Parameter(Mandatory = $true, ParameterSetName = 'ByName')] [ValidateNotNullOrEmpty()] [string]$PrinterName, [Parameter(Mandatory = $true, ParameterSetName = 'ByObject', ValueFromPipeline = $true)] [PSCustomObject]$InputObject, [Parameter(Mandatory = $true, Position = 0)] [ValidateNotNullOrEmpty()] [string]$FilePath, [Parameter(Position = 1)] [string]$Destination, [Parameter()] [switch]$StartPrint, [Parameter()] [switch]$PassThru ) process { # Resolve printer $resolveParams = @{} switch ($PSCmdlet.ParameterSetName) { 'ById' { $resolveParams['Id'] = $Id } 'ByName' { $resolveParams['PrinterName'] = $PrinterName } 'ByObject' { $resolveParams['InputObject'] = $InputObject } } $printer = Resolve-KlippyPrinterTarget @resolveParams # Resolve local file path $resolvedPath = Resolve-Path -Path $FilePath -ErrorAction Stop $fileInfo = Get-Item -Path $resolvedPath -ErrorAction Stop if ($fileInfo.PSIsContainer) { throw "Path '$FilePath' is a directory. Please specify a file." } # Determine destination filename $destFilename = $fileInfo.Name if ($Destination) { $cleanDest = $Destination.TrimStart('/').TrimEnd('/') if ($cleanDest -match '\.[^/]+$') { # Destination includes filename $destFilename = $cleanDest } else { # Destination is a folder $destFilename = "$cleanDest/$($fileInfo.Name)" } } $action = if ($StartPrint) { "Upload and start print" } else { "Upload file" } if ($PSCmdlet.ShouldProcess("$($fileInfo.FullName) -> $($printer.PrinterName):gcodes/$destFilename", $action)) { try { Write-Verbose "[$($printer.PrinterName)] Uploading: $($fileInfo.Name) -> $destFilename" # Build multipart form data $fileBytes = [System.IO.File]::ReadAllBytes($fileInfo.FullName) $boundary = [System.Guid]::NewGuid().ToString() $bodyLines = @( "--$boundary", "Content-Disposition: form-data; name=`"file`"; filename=`"$destFilename`"", "Content-Type: application/octet-stream", "" ) # Build body with file content $headerBytes = [System.Text.Encoding]::UTF8.GetBytes(($bodyLines -join "`r`n") + "`r`n") $footerBytes = [System.Text.Encoding]::UTF8.GetBytes("`r`n--$boundary--`r`n") $bodyStream = [System.IO.MemoryStream]::new() $bodyStream.Write($headerBytes, 0, $headerBytes.Length) $bodyStream.Write($fileBytes, 0, $fileBytes.Length) $bodyStream.Write($footerBytes, 0, $footerBytes.Length) $bodyContent = $bodyStream.ToArray() $bodyStream.Dispose() # Build URI $uri = "$($printer.Uri)/server/files/upload" if ($StartPrint) { $uri += "?print=true" } # Build headers $headers = @{ 'Content-Type' = "multipart/form-data; boundary=$boundary" } if ($printer.ApiKey) { $headers['X-Api-Key'] = $printer.ApiKey } # Upload file $response = Invoke-RestMethod -Uri $uri -Method POST -Headers $headers -Body $bodyContent -TimeoutSec 300 Write-Verbose "[$($printer.PrinterName)] Upload complete: $destFilename" if ($StartPrint) { Write-Verbose "[$($printer.PrinterName)] Print started" } if ($PassThru) { [PSCustomObject]@{ PSTypeName = 'KlippyCLI.GcodeFile' PrinterId = $printer.Id PrinterName = $printer.PrinterName Path = $response.item.path ?? $destFilename Name = Split-Path ($response.item.path ?? $destFilename) -Leaf Size = $fileInfo.Length SizeMB = [Math]::Round($fileInfo.Length / 1MB, 2) Modified = Get-Date } } } catch { Write-Error "[$($printer.PrinterName)] Failed to upload '$($fileInfo.Name)': $_" } } } } |