Functions/GenXdev.Windows/Set-ClipboardFiles.ps1
############################################################################### <# .SYNOPSIS Sets files to the Windows clipboard for file operations like copy/paste. .DESCRIPTION This function copies one or more file paths to the Windows clipboard, enabling file operations like paste in Windows Explorer. It handles both STA and MTA threading modes automatically, ensuring compatibility across different PowerShell execution contexts. The function validates file existence before adding paths to the clipboard. .PARAMETER InputObject Array of file paths to add to the clipboard. Accepts pipeline input and supports various aliases for compatibility with different object properties. .EXAMPLE Set-ClipboardFiles -InputObject "C:\temp\file1.txt", "C:\temp\file2.txt" Sets two files to the clipboard using full parameter names. .EXAMPLE "C:\temp\file1.txt", "C:\temp\file2.txt" | Set-ClipboardFiles Sets files to clipboard using pipeline input. .EXAMPLE ls * -file | select -first 5 | Set-ClipboardFiles Sets files to clipboard using pipeline input, selecting the first 5 files #> function Set-ClipboardFiles { [CmdletBinding(SupportsShouldProcess)] [Alias("setclipfiles")] [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseSingularNouns', '')] param ( ################################################################### [Parameter( Position = 0, Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, HelpMessage = ("Array of file paths to add to the clipboard") )] [Alias("Path", "FullName", "ImageFileName", "FileName")] [string[]]$InputObject ################################################################### ) begin { # initialize array to collect all file paths from pipeline and parameters $allFilePaths = @() } process { # collect file paths from pipeline input or direct parameter if ($null -ne $InputObject) { $allFilePaths += $InputObject } } end { # exit early if no file paths collected if ($allFilePaths.Count -eq 0) { return } # check if we should proceed with the operation if (-not $PSCmdlet.ShouldProcess("$($allFilePaths.Count) file(s)", "Set to clipboard")) { return } $done = @{} # validate each file path and collect only existing files $validFilePaths = $allFilePaths | Microsoft.PowerShell.Core\ForEach-Object { # expand the file path to absolute path $path = GenXdev.FileSystem\Expand-Path $_ if ($done.ContainsKey($path)) { # skip if this path has already been processed return } # mark this path as processed $done[$path] = $true # check if file exists and include in collection if ([System.IO.File]::Exists($path)) { $path return } if ([System.IO.Directory]::Exists($path)) { # if it's a directory, return it as well $path return } } # exit if no valid file paths found if (($null -eq $validFilePaths) -or ($validFilePaths.Count -eq 0)) { return } # get current thread apartment state for clipboard compatibility $apartmentState = [System.Threading.Thread]::CurrentThread.GetApartmentState() # check if running in single-threaded apartment mode if ($apartmentState -eq 'STA') { # load the system.windows.forms assembly for clipboard operations Microsoft.PowerShell.Utility\Add-Type -AssemblyName System.Windows.Forms # create a string collection for the file paths $fileCollection = Microsoft.PowerShell.Utility\New-Object ` System.Collections.Specialized.StringCollection # add each valid file path to the collection $validFilePaths | Microsoft.PowerShell.Core\ForEach-Object { $null = $fileCollection.Add($_) } # output verbose information about direct clipboard operation Microsoft.PowerShell.Utility\Write-Verbose ` ("Setting clipboard directly in STA mode with " + "$($fileCollection.Count) files") # set clipboard directly in sta mode [System.Windows.Forms.Clipboard]::SetFileDropList($fileCollection) } else { # output verbose information about sta subprocess requirement Microsoft.PowerShell.Utility\Write-Verbose ( "Current thread is MTA mode, launching STA subprocess " + "for clipboard operation") # convert file paths to json for subprocess parameter $jsonFilePaths = $validFilePaths | Microsoft.PowerShell.Utility\ConvertTo-Json -Compress # create a temporary file to store the json data $tempFile = GenXdev.FileSystem\Expand-Path ([System.IO.Path]::GetTempFileName()) -DeleteExistingFile -CreateDirectory $jsonFilePaths | Microsoft.PowerShell.Utility\Out-File -FilePath $tempFile # define the powershell command to execute in sta mode $command = ( "Microsoft.PowerShell.Utility\Add-Type -AssemblyName System.Windows.Forms;"+ "`$InputObject = Microsoft.PowerShell.Management\Get-Content '$tempFile' | "+ "Microsoft.PowerShell.Utility\ConvertFrom-Json -ErrorAction SilentlyContinue;"+ "Microsoft.PowerShell.Management\Remove-Item '$tempFile' -Force -ErrorAction SilentlyContinue;"+ "`$fileCollection = Microsoft.PowerShell.Utility\New-Object System.Collections.Specialized.StringCollection;"+ "`$InputObject | ForEach-Object { `$null = `$fileCollection.Add(`$_) };"+ "[System.Windows.Forms.Clipboard]::SetFileDropList(`$fileCollection);" ); try { # output verbose information about subprocess execution Microsoft.PowerShell.Utility\Write-Verbose ` "Executing STA subprocess for clipboard operation" # prepare arguments for powershell subprocess $pwshArgs = @( '-STA', '-NoProfile', '-Command', $command ) # start powershell subprocess in sta mode and wait for completion Microsoft.PowerShell.Management\Start-Process ` -FilePath 'pwsh' ` -ArgumentList $pwshArgs ` -NoNewWindow ` -Wait } catch { # cleanup temp file in case of error if (Microsoft.PowerShell.Management\Test-Path $tempFile) { Microsoft.PowerShell.Management\Remove-Item $tempFile -Force -ErrorAction SilentlyContinue } # output error if subprocess execution fails Microsoft.PowerShell.Utility\Write-Error ` "Error invoking pwsh: $_" } } } } ############################################################################### |