Functions/GenXdev.Windows/Get-ClipboardFiles.cs
// ################################################################################
// Part of PowerShell module : GenXdev.Windows // Original cmdlet filename : Get-ClipboardFiles.cs // Original author : René Vaessen / GenXdev // Version : 1.302.2025 // ################################################################################ // Copyright (c) René Vaessen / GenXdev // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // ################################################################################ using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Management.Automation; using System.Windows.Forms; namespace GenXdev.Windows { /// <summary> /// <para type="synopsis"> /// Gets files from the Windows clipboard that were set for file operations like copy/paste. /// </para> /// /// <para type="description"> /// This function retrieves file paths from the Windows clipboard that were previously /// set for file operations. It handles both STA and MTA threading modes automatically, /// ensuring compatibility across different PowerShell execution contexts. The function /// validates file existence and returns only existing files/directories as objects /// similar to Get-ChildItem or Get-Item output. /// </para> /// /// <example> /// <para>Get all files currently in the clipboard and returns them as file system objects.</para> /// <code> /// Get-ClipboardFiles /// </code> /// </example> /// /// <example> /// <para>Get clipboard files and displays their full paths.</para> /// <code> /// $clipboardFiles = Get-ClipboardFiles /// $clipboardFiles | ForEach-Object { Write-Host $_.FullName } /// </code> /// </example> /// /// <example> /// <para>Get only text files from the clipboard.</para> /// <code> /// Get-ClipboardFiles | Where-Object { $_.Extension -eq '.txt' } /// </code> /// </example> /// /// <example> /// <para>Get all files from the clipboard and lists them in a detailed format.</para> /// <code> /// Get-ClipboardFiles | ls /// </code> /// </example> /// </summary> [Cmdlet(VerbsCommon.Get, "ClipboardFiles")] [Alias("getclipfiles")] [OutputType(typeof(PSObject))] public class GetClipboardFilesCommand : PSGenXdevCmdlet { /// <summary> /// Begin processing - initialization logic /// </summary> protected override void BeginProcessing() { } /// <summary> /// Process record - main cmdlet logic /// </summary> protected override void ProcessRecord() { // Get current thread apartment state for clipboard compatibility var apartmentState = System.Threading.Thread.CurrentThread.GetApartmentState(); // Initialize collection for file paths var clipboardFilePaths = new List<string>(); // Check if running in single-threaded apartment mode if (apartmentState == System.Threading.ApartmentState.STA) { // Output verbose information about direct clipboard operation WriteVerbose("Getting clipboard files directly in STA mode"); try { // Get file drop list from clipboard in STA mode var fileDropList = Clipboard.GetFileDropList(); if (fileDropList != null) { foreach (string file in fileDropList) { clipboardFilePaths.Add(file); } } } catch { // Output verbose information if clipboard doesn't contain files WriteVerbose("No file drop list found in clipboard or clipboard access failed"); return; } } else { // Output verbose information about STA subprocess requirement WriteVerbose("Current thread is MTA mode, launching STA subprocess for clipboard operation"); // Create a temporary file to receive the JSON data var tempFile = Path.GetTempFileName(); // Define the PowerShell command to execute in STA mode var command = "Add-Type -AssemblyName System.Windows.Forms;" + "try {" + "$fileDropList = [System.Windows.Forms.Clipboard]::GetFileDropList();" + "if ($null -ne $fileDropList) {" + "$paths = $fileDropList | ForEach-Object { $_ };" + "$paths | ConvertTo-Json -Compress | Out-File '" + tempFile.Replace("'", "''") + "';" + "} else { '[]' | Out-File '" + tempFile.Replace("'", "''") + "'; }" + "} catch { '[]' | Out-File '" + tempFile.Replace("'", "''") + "'; }"; try { // Output verbose information about subprocess execution WriteVerbose("Executing STA subprocess for clipboard operation"); // Prepare arguments for PowerShell subprocess var pwshArgs = new[] { "-STA", "-NoProfile", "-Command", command }; // Start PowerShell subprocess in STA mode and wait for completion var process = Process.Start(new ProcessStartInfo { FileName = "pwsh", Arguments = string.Join(" ", pwshArgs), UseShellExecute = false, CreateNoWindow = true }); process.WaitForExit(); // Read the result from temp file if (File.Exists(tempFile)) { var jsonContent = File.ReadAllText(tempFile); if (!string.IsNullOrWhiteSpace(jsonContent)) { var paths = System.Text.Json.JsonSerializer.Deserialize<string[]>(jsonContent); if (paths != null) { clipboardFilePaths.AddRange(paths); } } File.Delete(tempFile); } } catch { // Cleanup temp file in case of error if (File.Exists(tempFile)) { File.Delete(tempFile); } // Output error if subprocess execution fails WriteError(new ErrorRecord( new Exception("Error invoking pwsh"), "ClipboardAccessError", ErrorCategory.NotSpecified, null)); return; } } // Exit early if no file paths retrieved if (clipboardFilePaths == null || clipboardFilePaths.Count == 0) { return; } var done = new HashSet<string>(); // Validate each file path and collect only existing files/directories foreach (var filePath in clipboardFilePaths) { // Expand the file path to absolute path string path = ExpandPath(filePath); if (done.Contains(path)) { // Skip if this path has already been processed continue; } // Mark this path as processed done.Add(path); // Check if file exists and return as file system object if (File.Exists(path)) { // Return file object similar to Get-Item var getItemScript = ScriptBlock.Create("param($p) Get-Item -LiteralPath $p"); var result = getItemScript.Invoke(path); WriteObject(result[0]); continue; } if (Directory.Exists(path)) { // Return directory object similar to Get-Item var getItemScript = ScriptBlock.Create("param($p) Get-Item -LiteralPath $p"); var result = getItemScript.Invoke(path); WriteObject(result[0]); continue; } } // Output verbose information about results if (clipboardFilePaths.Count > 0) { WriteVerbose($"Retrieved {clipboardFilePaths.Count} valid file/directory objects from clipboard"); } } /// <summary> /// End processing - cleanup logic /// </summary> protected override void EndProcessing() { } } } |