diff --git a/assets/files.wxs b/assets/files.wxs index 5869628e0cd..86baf9f5d09 100644 --- a/assets/files.wxs +++ b/assets/files.wxs @@ -1331,9 +1331,6 @@ - - - @@ -1643,6 +1640,9 @@ + + + @@ -3919,6 +3919,7 @@ + diff --git a/src/Microsoft.PowerShell.Commands.Management/Microsoft.PowerShell.Commands.Management.csproj b/src/Microsoft.PowerShell.Commands.Management/Microsoft.PowerShell.Commands.Management.csproj index cd84d055c08..26f8f161a51 100644 --- a/src/Microsoft.PowerShell.Commands.Management/Microsoft.PowerShell.Commands.Management.csproj +++ b/src/Microsoft.PowerShell.Commands.Management/Microsoft.PowerShell.Commands.Management.csproj @@ -18,14 +18,12 @@ - - @@ -38,7 +36,6 @@ - @@ -46,7 +43,6 @@ - diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/Clipboard.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/Clipboard.cs new file mode 100644 index 00000000000..95f276f792b --- /dev/null +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/Clipboard.cs @@ -0,0 +1,386 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Threading; + +namespace Microsoft.PowerShell.Commands.Internal +{ + internal static class Clipboard + { + private static bool? _clipboardSupported; + + // Used if an external clipboard is not available, e.g. if xclip is missing. + // This is useful for testing in CI as well. + private static string _internalClipboard; + + private static string StartProcess( + string tool, + string args, + string stdin = "" ) + { + ProcessStartInfo startInfo = new ProcessStartInfo(); + startInfo.UseShellExecute = false; + startInfo.RedirectStandardInput = true; + startInfo.RedirectStandardOutput = true; + startInfo.RedirectStandardError = true; + startInfo.FileName = tool; + startInfo.Arguments = args; + string stdout; + + using (Process process = new Process()) + { + process.StartInfo = startInfo; + try + { + process.Start(); + } + catch (System.ComponentModel.Win32Exception) + { + _clipboardSupported = false; + return string.Empty; + } + + if (!string.IsNullOrEmpty(stdin)) + { + process.StandardInput.Write(stdin); + process.StandardInput.Close(); + } + + stdout = process.StandardOutput.ReadToEnd(); + process.WaitForExit(250); + + _clipboardSupported = process.ExitCode == 0; + } + + return stdout; + } + + public static string GetText() + { + if (_clipboardSupported == false) + { + return _internalClipboard ?? string.Empty; + } + + string tool = string.Empty; + string args = string.Empty; + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + string clipboardText = string.Empty; + ExecuteOnStaThread(() => GetTextImpl(out clipboardText)); + return clipboardText; + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + { + tool = "xclip"; + args = "-selection clipboard -out"; + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + tool = "pbpaste"; + } + else + { + _clipboardSupported = false; + return string.Empty; + } + + return StartProcess(tool, args); + } + + public static void SetText(string text) + { + if (string.IsNullOrEmpty(text)) + { + return; + } + + if (_clipboardSupported == false) + { + _internalClipboard = text; + return; + } + + string tool = string.Empty; + string args = string.Empty; + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + ExecuteOnStaThread(() => SetClipboardData(Tuple.Create(text, CF_UNICODETEXT))); + return; + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + { + tool = "xclip"; + args = "-selection clipboard -in"; + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + tool = "pbcopy"; + } + else + { + _clipboardSupported = false; + return; + } + + StartProcess(tool, args, text); + if (_clipboardSupported == false) + { + _internalClipboard = text; + } + } + + public static void SetRtf(string plainText, string rtfText) + { + if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + return; + } + + if (s_CF_RTF == 0) + { + s_CF_RTF = RegisterClipboardFormat("Rich Text Format"); + } + + ExecuteOnStaThread(() => SetClipboardData( + Tuple.Create(plainText, CF_UNICODETEXT), + Tuple.Create(rtfText, s_CF_RTF))); + } + + private const uint GMEM_MOVEABLE = 0x0002; + private const uint GMEM_ZEROINIT = 0x0040; + private const uint GHND = GMEM_MOVEABLE | GMEM_ZEROINIT; + + [DllImport("kernel32.dll")] + private static extern IntPtr GlobalAlloc(uint flags, UIntPtr dwBytes); + + [DllImport("kernel32.dll")] + private static extern IntPtr GlobalFree(IntPtr hMem); + + [DllImport("kernel32.dll")] + private static extern IntPtr GlobalLock(IntPtr hMem); + + [DllImport("kernel32.dll")] + [return: MarshalAs(UnmanagedType.Bool)] + private static extern bool GlobalUnlock(IntPtr hMem); + + [DllImport("kernel32.dll", ExactSpelling = true, EntryPoint = "RtlMoveMemory", SetLastError = true)] + private static extern void CopyMemory(IntPtr dest, IntPtr src, uint count); + + [DllImport("user32.dll", SetLastError = false)] + [return: MarshalAs(UnmanagedType.Bool)] + private static extern bool IsClipboardFormatAvailable(uint format); + + [DllImport("user32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + private static extern bool OpenClipboard(IntPtr hWndNewOwner); + + [DllImport("user32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + private static extern bool CloseClipboard(); + + [DllImport("user32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + private static extern bool EmptyClipboard(); + + [DllImport("user32.dll", SetLastError = true)] + private static extern IntPtr GetClipboardData(uint format); + + [DllImport("user32.dll")] + private static extern IntPtr SetClipboardData(uint format, IntPtr data); + + [DllImport("user32.dll", SetLastError = true)] + private static extern uint RegisterClipboardFormat(string lpszFormat); + + private const uint CF_TEXT = 1; + private const uint CF_UNICODETEXT = 13; + private static uint s_CF_RTF; + + private static bool GetTextImpl(out string text) + { + try + { + if (IsClipboardFormatAvailable(CF_UNICODETEXT)) + { + if (OpenClipboard(IntPtr.Zero)) + { + var data = GetClipboardData(CF_UNICODETEXT); + if (data != IntPtr.Zero) + { + data = GlobalLock(data); + text = Marshal.PtrToStringUni(data); + GlobalUnlock(data); + return true; + } + } + } + else if (IsClipboardFormatAvailable(CF_TEXT)) + { + if (OpenClipboard(IntPtr.Zero)) + { + var data = GetClipboardData(CF_TEXT); + if (data != IntPtr.Zero) + { + data = GlobalLock(data); + text = Marshal.PtrToStringAnsi(data); + GlobalUnlock(data); + return true; + } + } + } + } + catch + { + // Ignore exceptions + } + finally + { + CloseClipboard(); + } + + text = string.Empty; + return false; + } + + private static bool SetClipboardData(params Tuple[] data) + { + try + { + if (!OpenClipboard(IntPtr.Zero)) + { + return false; + } + + EmptyClipboard(); + + foreach (var d in data) + { + if (!SetSingleClipboardData(d.Item1, d.Item2)) + { + return false; + } + } + } + finally + { + CloseClipboard(); + } + + return true; + } + + private static bool SetSingleClipboardData(string text, uint format) + { + IntPtr hGlobal = IntPtr.Zero; + IntPtr data = IntPtr.Zero; + + try + { + uint bytes; + if (format == s_CF_RTF || format == CF_TEXT) + { + bytes = (uint)(text.Length + 1); + data = Marshal.StringToHGlobalAnsi(text); + } + else if (format == CF_UNICODETEXT) + { + bytes = (uint)((text.Length + 1) * 2); + data = Marshal.StringToHGlobalUni(text); + } + else + { + // Not yet supported format. + return false; + } + + if (data == IntPtr.Zero) + { + return false; + } + + hGlobal = GlobalAlloc(GHND, (UIntPtr)bytes); + if (hGlobal == IntPtr.Zero) + { + return false; + } + + IntPtr dataCopy = GlobalLock(hGlobal); + if (dataCopy == IntPtr.Zero) + { + return false; + } + + CopyMemory(dataCopy, data, bytes); + GlobalUnlock(hGlobal); + + if (SetClipboardData(format, hGlobal) != IntPtr.Zero) + { + // The clipboard owns this memory now, so don't free it. + hGlobal = IntPtr.Zero; + } + } + catch + { + // Ignore failures + } + finally + { + if (data != IntPtr.Zero) + { + Marshal.FreeHGlobal(data); + } + + if (hGlobal != IntPtr.Zero) + { + GlobalFree(hGlobal); + } + } + + return true; + } + + private static void ExecuteOnStaThread(Func action) + { + const int RetryCount = 5; + int tries = 0; + + if (Thread.CurrentThread.GetApartmentState() == ApartmentState.STA) + { + while (tries++ < RetryCount && !action()) + { + // wait until RetryCount or action + } + + return; + } + + Exception exception = null; + var thread = new Thread(() => + { + try + { + while (tries++ < RetryCount && !action()) + { + // wait until RetryCount or action + } + } + catch (Exception e) + { + exception = e; + } + }); + + thread.SetApartmentState(ApartmentState.STA); + thread.Start(); + thread.Join(); + + if (exception != null) + { + throw exception; + } + } + } +} diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/GetClipboardCommand.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/GetClipboardCommand.cs index f033b13100b..7dda142c2eb 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/GetClipboardCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/GetClipboardCommand.cs @@ -3,72 +3,20 @@ using System; using System.Collections.Generic; -using System.Collections.Specialized; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Drawing; -using System.Globalization; -using System.IO; -using System.Linq; using System.Management.Automation; -using System.Media; -using System.Runtime.InteropServices; -using System.Windows.Forms; +using Microsoft.PowerShell.Commands.Internal; namespace Microsoft.PowerShell.Commands { - /// - /// Defines the different type supported by the clipboard. - /// - public enum ClipboardFormat - { - /// Text format as default. - Text = 0, - - /// File format. - FileDropList = 1, - - /// Image format. - Image = 2, - - /// Audio format. - Audio = 3, - }; - /// /// Defines the implementation of the 'Get-Clipboard' cmdlet. /// This cmdlet get the content from system clipboard. /// - [Cmdlet(VerbsCommon.Get, "Clipboard", HelpUri = "https://go.microsoft.com/fwlink/?LinkId=526219")] + [Cmdlet(VerbsCommon.Get, "Clipboard", HelpUri = "https://go.microsoft.com/fwlink/?LinkId=2109905")] [Alias("gcb")] - [OutputType(typeof(string), typeof(FileInfo), typeof(Image), typeof(Stream))] + [OutputType(typeof(string))] public class GetClipboardCommand : PSCmdlet { - /// - /// Property that sets clipboard type. This will return the required format from clipboard. - /// - [Parameter] - public ClipboardFormat Format { get; set; } - - /// - /// Property that sets format type when the return type is text. - /// - [Parameter] - [ValidateNotNullOrEmpty] - public TextDataFormat TextFormatType - { - get { return _textFormat; } - - set - { - _isTextFormatTypeSet = true; - _textFormat = value; - } - } - - private TextDataFormat _textFormat = TextDataFormat.UnicodeText; - private bool _isTextFormatTypeSet = false; - /// /// Property that sets raw parameter. This will allow clipboard return text or file list as one string. /// @@ -79,76 +27,38 @@ public SwitchParameter Raw set { - _isRawSet = true; _raw = value; } } private bool _raw; - private bool _isRawSet = false; /// /// This method implements the ProcessRecord method for Get-Clipboard command. /// protected override void BeginProcessing() { - // TextFormatType should only combine with Text. - if (Format != ClipboardFormat.Text && _isTextFormatTypeSet) - { - ThrowTerminatingError(new ErrorRecord(new InvalidOperationException( - string.Format(CultureInfo.InvariantCulture, ClipboardResources.InvalidTypeCombine)), - "FailedToGetClipboard", ErrorCategory.InvalidOperation, "Clipboard")); - } - - // Raw should only combine with Text or FileDropList. - if (Format != ClipboardFormat.Text && Format != ClipboardFormat.FileDropList && _isRawSet) - { - ThrowTerminatingError(new ErrorRecord(new InvalidOperationException( - string.Format(CultureInfo.InvariantCulture, ClipboardResources.InvalidRawCombine)), - "FailedToGetClipboard", ErrorCategory.InvalidOperation, "Clipboard")); - } - - if (Format == ClipboardFormat.Text) - { - this.WriteObject(GetClipboardContentAsText(_textFormat), true); - } - else if (Format == ClipboardFormat.Image) - { - this.WriteObject(Clipboard.GetImage()); - } - else if (Format == ClipboardFormat.FileDropList) - { - if (_raw) - { - this.WriteObject(Clipboard.GetFileDropList(), true); - } - else - { - this.WriteObject(GetClipboardContentAsFileList()); - } - } - else if (Format == ClipboardFormat.Audio) - { - this.WriteObject(Clipboard.GetAudioStream()); - } + this.WriteObject(GetClipboardContentAsText(), true); } /// /// Returns the clipboard content as text format. /// - /// - /// - private List GetClipboardContentAsText(TextDataFormat textFormat) + /// Array of strings representing content from clipboard. + private List GetClipboardContentAsText() { - if (!Clipboard.ContainsText(textFormat)) + var result = new List(); + string textContent = null; + + try { - return null; + textContent = Clipboard.GetText(); + } + catch (PlatformNotSupportedException) + { + ThrowTerminatingError(new ErrorRecord(new InvalidOperationException(ClipboardResources.UnsupportedPlatform), "FailedToGetClipboardUnsupportedPlatform", ErrorCategory.InvalidOperation, "Clipboard")); } - List result = new List(); - - // TextFormat default value is Text, by default it is same as Clipboard.GetText() - string textContent = Clipboard.GetText(textFormat); if (_raw) { result.Add(textContent); @@ -161,54 +71,5 @@ private List GetClipboardContentAsText(TextDataFormat textFormat) return result; } - - /// - /// Returns the clipboard content as file info. - /// - /// - private List GetClipboardContentAsFileList() - { - if (!Clipboard.ContainsFileDropList()) - { - return null; - } - - List result = new List(); - foreach (string filePath in Clipboard.GetFileDropList()) - { - FileInfo file = new FileInfo(filePath); - result.Add(WrapOutputInPSObject(file, filePath)); - } - - return result; - } - - /// - /// Wraps the item in a PSObject and attaches some notes to the - /// object that deal with path information. - /// - /// - /// - /// - private PSObject WrapOutputInPSObject( - FileInfo item, - string path) - { - PSObject result = new PSObject(item); - - // Now get the parent path and child name - if (path != null) - { - // Get the parent path - string parentPath = Directory.GetParent(path).FullName; - result.AddOrSetProperty("PSParentPath", parentPath); - - // Get the child name - string childName = item.Name; - result.AddOrSetProperty("PSChildName", childName); - } - - return result; - } } } diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/SetClipboardCommand.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/SetClipboardCommand.cs index bce2f7ec14e..3aed047f443 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/SetClipboardCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/SetClipboardCommand.cs @@ -3,17 +3,11 @@ using System; using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Collections.Specialized; -using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Globalization; -using System.IO; -using System.Linq; using System.Management.Automation; using System.Text; -using System.Text.RegularExpressions; -using System.Windows.Forms; +using Microsoft.PowerShell.Commands.Internal; namespace Microsoft.PowerShell.Commands { @@ -21,20 +15,17 @@ namespace Microsoft.PowerShell.Commands /// Defines the implementation of the 'Set-Clipboard' cmdlet. /// This cmdlet gets the content from system clipboard. /// - [Cmdlet(VerbsCommon.Set, "Clipboard", DefaultParameterSetName = "String", SupportsShouldProcess = true, HelpUri = "https://go.microsoft.com/fwlink/?LinkId=526220")] + [Cmdlet(VerbsCommon.Set, "Clipboard", SupportsShouldProcess = true, ConfirmImpact = ConfirmImpact.Medium, HelpUri = "https://go.microsoft.com/fwlink/?LinkId=2109826")] [Alias("scb")] public class SetClipboardCommand : PSCmdlet { private List _contentList = new List(); - private const string ValueParameterSet = "Value"; - private const string PathParameterSet = "Path"; - private const string LiteralPathParameterSet = "LiteralPath"; /// /// Property that sets clipboard content. /// - [Parameter(ParameterSetName = ValueParameterSet, Position = 0, Mandatory = true, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true)] - [AllowNull] + [Parameter(Position = 0, Mandatory = true, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true)] + [System.Management.Automation.AllowNull] [AllowEmptyCollection] [AllowEmptyString] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] @@ -46,41 +37,6 @@ public class SetClipboardCommand : PSCmdlet [Parameter] public SwitchParameter Append { get; set; } - /// - /// Property that sets Path parameter. This will allow to set file formats to Clipboard. - /// - [Parameter(ParameterSetName = PathParameterSet, Mandatory = true, ValueFromPipelineByPropertyName = true)] - [ValidateNotNullOrEmpty] - [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] - public string[] Path { get; set; } - - /// - /// Property that sets LiteralPath parameter. This will allow to set file formats to Clipboard. - /// - [Parameter(ParameterSetName = LiteralPathParameterSet, Mandatory = true, ValueFromPipelineByPropertyName = true)] - [Alias("PSPath")] - [ValidateNotNullOrEmpty] - [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] - public string[] LiteralPath { get; set; } - - /// - /// Property that sets html parameter. This will allow html content rendered as html to clipboard. - /// - [Parameter] - public SwitchParameter AsHtml - { - get { return _asHtml; } - - set - { - _isHtmlSet = true; - _asHtml = value; - } - } - - private bool _asHtml; - private bool _isHtmlSet = false; - /// /// This method implements the BeginProcessing method for Set-Clipboard command. /// @@ -94,26 +50,10 @@ protected override void BeginProcessing() /// protected override void ProcessRecord() { - // Html should only combine with Text content. - if (Value == null && _isHtmlSet) - { - ThrowTerminatingError(new ErrorRecord(new InvalidOperationException( - string.Format(CultureInfo.InvariantCulture, ClipboardResources.InvalidHtmlCombine)), - "FailedToSetClipboard", ErrorCategory.InvalidOperation, "Clipboard")); - } - if (Value != null) { _contentList.AddRange(Value); } - else if (Path != null) - { - _contentList.AddRange(Path); - } - else if (LiteralPath != null) - { - _contentList.AddRange(LiteralPath); - } } /// @@ -121,27 +61,15 @@ protected override void ProcessRecord() /// protected override void EndProcessing() { - if (LiteralPath != null) - { - CopyFilesToClipboard(_contentList, Append, true); - } - else if (Path != null) - { - CopyFilesToClipboard(_contentList, Append, false); - } - else - { - SetClipboardContent(_contentList, Append, _asHtml); - } + SetClipboardContent(_contentList, Append); } /// /// Set the clipboard content. /// - /// - /// - /// - private void SetClipboardContent(List contentList, bool append, bool asHtml) + /// The content to store into the clipboard. + /// If true, appends to clipboard instead of overwriting. + private void SetClipboardContent(List contentList, bool append) { string setClipboardShouldProcessTarget; @@ -150,7 +78,7 @@ private void SetClipboardContent(List contentList, bool append, bool asH setClipboardShouldProcessTarget = string.Format(CultureInfo.InvariantCulture, ClipboardResources.ClipboardCleared); if (ShouldProcess(setClipboardShouldProcessTarget, "Set-Clipboard")) { - Clipboard.Clear(); + Clipboard.SetText(string.Empty); } return; @@ -159,15 +87,7 @@ private void SetClipboardContent(List contentList, bool append, bool asH StringBuilder content = new StringBuilder(); if (append) { - if (!Clipboard.ContainsText()) - { - WriteVerbose(string.Format(CultureInfo.InvariantCulture, ClipboardResources.NoAppendableClipboardContent)); - append = false; - } - else - { - content.AppendLine(Clipboard.GetText()); - } + content.AppendLine(Clipboard.GetText()); } if (contentList != null) @@ -175,7 +95,6 @@ private void SetClipboardContent(List contentList, bool append, bool asH content.Append(string.Join(Environment.NewLine, contentList.ToArray(), 0, contentList.Count)); } - // Verbose output string verboseString = null; if (contentList != null) { @@ -198,230 +117,8 @@ private void SetClipboardContent(List contentList, bool append, bool asH if (ShouldProcess(setClipboardShouldProcessTarget, "Set-Clipboard")) { - // Set the text data - Clipboard.Clear(); - if (asHtml) - Clipboard.SetText(GetHtmlDataString(content.ToString()), TextDataFormat.Html); - else - Clipboard.SetText(content.ToString()); - } - } - - /// - /// Copy the file format to clipboard. - /// - /// - /// - /// - private void CopyFilesToClipboard(List fileList, bool append, bool isLiteralPath) - { - int clipBoardContentLength = 0; - HashSet dropFiles = new HashSet(StringComparer.OrdinalIgnoreCase); - - // Append the new file list after the file list exists in the clipboard. - if (append) - { - if (!Clipboard.ContainsFileDropList()) - { - WriteVerbose(string.Format(CultureInfo.InvariantCulture, ClipboardResources.NoAppendableClipboardContent)); - append = false; - } - else - { - StringCollection clipBoardContent = Clipboard.GetFileDropList(); - dropFiles = new HashSet(clipBoardContent.Cast().ToList(), StringComparer.OrdinalIgnoreCase); - - // we need the count of original files so we can get the accurate files number that has been appended. - clipBoardContentLength = clipBoardContent.Count; - } - } - - ProviderInfo provider = null; - for (int i = 0; i < fileList.Count; i++) - { - Collection newPaths = new Collection(); - - try - { - if (isLiteralPath) - { - newPaths.Add(Context.SessionState.Path.GetUnresolvedProviderPathFromPSPath(fileList[i])); - } - else - { - newPaths = Context.SessionState.Path.GetResolvedProviderPathFromPSPath(fileList[i], out provider); - } - } - catch (ItemNotFoundException exception) - { - WriteError(new ErrorRecord(exception, "FailedToSetClipboard", ErrorCategory.InvalidOperation, "Clipboard")); - } - - foreach (string fileName in newPaths) - { - // Avoid adding duplicated files. - if (!dropFiles.Contains(fileName)) - { - dropFiles.Add(fileName); - } - } - } - - if (dropFiles.Count == 0) - return; - - // Verbose output - string setClipboardShouldProcessTarget; - if ((dropFiles.Count - clipBoardContentLength) == 1) - { - if (append) - { - setClipboardShouldProcessTarget = string.Format(CultureInfo.InvariantCulture, ClipboardResources.AppendSingleFileToClipboard, dropFiles.ElementAt(dropFiles.Count - 1)); - } - else - { - setClipboardShouldProcessTarget = string.Format(CultureInfo.InvariantCulture, ClipboardResources.SetSingleFileToClipboard, dropFiles.ElementAt(0)); - } - } - else - { - if (append) - { - setClipboardShouldProcessTarget = string.Format(CultureInfo.InvariantCulture, ClipboardResources.AppendMultipleFilesToClipboard, (dropFiles.Count - clipBoardContentLength)); - } - else - { - setClipboardShouldProcessTarget = string.Format(CultureInfo.InvariantCulture, ClipboardResources.SetMultipleFilesToClipboard, dropFiles.Count); - } - } - - if (ShouldProcess(setClipboardShouldProcessTarget, "Set-Clipboard")) - { - // Set file list formats to clipboard. - Clipboard.Clear(); - StringCollection fileDropList = new StringCollection(); - fileDropList.AddRange(dropFiles.ToArray()); - Clipboard.SetFileDropList(fileDropList); + Clipboard.SetText(content.ToString()); } } - - /// - /// Generate HTML fragment data string with header that is required for the clipboard. - /// - /// The html to generate for. - /// The resulted string. - private static string GetHtmlDataString(string html) - { - // The string contains index references to other spots in the string, so we need placeholders so we can compute the offsets. - // The "<<<<<<<<1,<<<<<<<<2, etc" strings are just placeholders. We'll back-patch them actual values within the header location afterwards. - const string Header = @"Version:0.9 -StartHTML:<<<<<<<<1 -EndHTML:<<<<<<<<2 -StartFragment:<<<<<<<<3 -EndFragment:<<<<<<<<4 -StartSelection:<<<<<<<<3 -EndSelection:<<<<<<<<4"; - - const string StartFragment = ""; - const string EndFragment = @""; - - var sb = new StringBuilder(); - sb.AppendLine(Header); - sb.AppendLine(@""); - - // if given html already provided the fragments we won't add them - int fragmentStart, fragmentEnd; - int fragmentStartIdx = html.IndexOf(StartFragment, StringComparison.OrdinalIgnoreCase); - int fragmentEndIdx = html.LastIndexOf(EndFragment, StringComparison.OrdinalIgnoreCase); - - // if html tag is missing add it surrounding the given html - // find the index of " 0 ? html.IndexOf('>', htmlOpenIdx) + 1 : -1; - // find the index of " 0 ? html.IndexOf('>', bodyOpenIdx) + 1 : -1; - - if (htmlOpenEndIdx < 0 && bodyOpenEndIdx < 0) - { - // the given html doesn't contain html or body tags so we need to add them and place start/end fragments around the given html only - sb.Append(""); - sb.Append(StartFragment); - fragmentStart = GetByteCount(sb); - sb.Append(html); - fragmentEnd = GetByteCount(sb); - sb.Append(EndFragment); - sb.Append(""); - } - else - { - // insert start/end fragments in the proper place (related to html/body tags if exists) so the paste will work correctly - // find the index of ""); - else - sb.Append(html, 0, htmlOpenEndIdx); - - if (bodyOpenEndIdx > -1) - sb.Append(html, htmlOpenEndIdx > -1 ? htmlOpenEndIdx : 0, bodyOpenEndIdx - (htmlOpenEndIdx > -1 ? htmlOpenEndIdx : 0)); - - sb.Append(StartFragment); - fragmentStart = GetByteCount(sb); - - var innerHtmlStart = bodyOpenEndIdx > -1 ? bodyOpenEndIdx : (htmlOpenEndIdx > -1 ? htmlOpenEndIdx : 0); - var innerHtmlEnd = bodyCloseIdx > 0 ? bodyCloseIdx : (htmlCloseIdx > 0 ? htmlCloseIdx : html.Length); - sb.Append(html, innerHtmlStart, innerHtmlEnd - innerHtmlStart); - - fragmentEnd = GetByteCount(sb); - sb.Append(EndFragment); - - if (innerHtmlEnd < html.Length) - sb.Append(html, innerHtmlEnd, html.Length - innerHtmlEnd); - - if (htmlCloseIdx <= 0) - sb.Append(""); - } - } - else - { - // directly return the cf_html - return html; - } - - // Back-patch offsets, the replace text area is restricted to header only from index 0 to header.Length - sb.Replace("<<<<<<<<1", Header.Length.ToString("D9", CultureInfo.CreateSpecificCulture("en-US")), 0, Header.Length); - sb.Replace("<<<<<<<<2", GetByteCount(sb).ToString("D9", CultureInfo.CreateSpecificCulture("en-US")), 0, Header.Length); - sb.Replace("<<<<<<<<3", fragmentStart.ToString("D9", CultureInfo.CreateSpecificCulture("en-US")), 0, Header.Length); - sb.Replace("<<<<<<<<4", fragmentEnd.ToString("D9", CultureInfo.CreateSpecificCulture("en-US")), 0, Header.Length); - return sb.ToString(); - } - - /// - /// Calculates the number of bytes produced by encoding the string in the string builder in UTF-8 and not .NET default string encoding. - /// - /// The string builder to count its string. - /// Optional: the start index to calculate from (default - start of string). - /// Optional: the end index to calculate to (default - end of string). - /// The number of bytes required to encode the string in UTF-8. - private static int GetByteCount(StringBuilder sb, int start = 0, int end = -1) - { - char[] _byteCount = new char[1]; - int count = 0; - end = end > -1 ? end : sb.Length; - for (int i = start; i < end; i++) - { - _byteCount[0] = sb[i]; - count += Encoding.UTF8.GetByteCount(_byteCount); - } - - return count; - } } } diff --git a/src/Microsoft.PowerShell.Commands.Management/resources/ClipboardResources.resx b/src/Microsoft.PowerShell.Commands.Management/resources/ClipboardResources.resx index e306a959940..9e6cbd55cc1 100644 --- a/src/Microsoft.PowerShell.Commands.Management/resources/ClipboardResources.resx +++ b/src/Microsoft.PowerShell.Commands.Management/resources/ClipboardResources.resx @@ -131,4 +131,22 @@ Html can only be combined with Html Text format. + + Only Text format is supported on this platform. + + + The clipboard is not supported on this platform. + + + The '-AsHtml' switch is not supported on this platform. + + + The '-TextFormatType' parameter only supports 'Text' on this platform. + + + The '-Path' parameter is not supported on this platform. + + + The '-LiteralPath' parameter is not supported on this platform. + diff --git a/src/Modules/Unix/Microsoft.PowerShell.Management/Microsoft.PowerShell.Management.psd1 b/src/Modules/Unix/Microsoft.PowerShell.Management/Microsoft.PowerShell.Management.psd1 index 540ad83e280..547358125d7 100644 --- a/src/Modules/Unix/Microsoft.PowerShell.Management/Microsoft.PowerShell.Management.psd1 +++ b/src/Modules/Unix/Microsoft.PowerShell.Management/Microsoft.PowerShell.Management.psd1 @@ -9,7 +9,7 @@ PowerShellVersion="3.0" NestedModules="Microsoft.PowerShell.Commands.Management.dll" HelpInfoURI = 'https://go.microsoft.com/fwlink/?linkid=855958' FunctionsToExport = @() -AliasesToExport = @("gtz") +AliasesToExport = @("gcb", "gtz", "scb") CmdletsToExport=@("Add-Content", "Clear-Content", "Clear-ItemProperty", @@ -17,6 +17,8 @@ CmdletsToExport=@("Add-Content", "Convert-Path", "Copy-ItemProperty", "Get-ChildItem", + "Get-Clipboard", + "Set-Clipboard", "Get-Content", "Get-ItemProperty", "Get-ItemPropertyValue", diff --git a/src/Modules/Windows/Microsoft.PowerShell.Management/Microsoft.PowerShell.Management.psd1 b/src/Modules/Windows/Microsoft.PowerShell.Management/Microsoft.PowerShell.Management.psd1 index b3edf3eab77..48176476d5a 100644 --- a/src/Modules/Windows/Microsoft.PowerShell.Management/Microsoft.PowerShell.Management.psd1 +++ b/src/Modules/Windows/Microsoft.PowerShell.Management/Microsoft.PowerShell.Management.psd1 @@ -9,9 +9,11 @@ PowerShellVersion="3.0" NestedModules="Microsoft.PowerShell.Commands.Management.dll" HelpInfoURI = 'https://go.microsoft.com/fwlink/?linkid=855958' FunctionsToExport = @() -AliasesToExport = @("gin", "gtz", "stz") +AliasesToExport = @("gcb", "gin", "gtz", "scb", "stz") CmdletsToExport=@("Add-Content", "Clear-Content", + "Get-Clipboard", + "Set-Clipboard", "Clear-ItemProperty", "Join-Path", "Convert-Path", diff --git a/test/powershell/Modules/Microsoft.PowerShell.Management/Clipboard.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Management/Clipboard.Tests.ps1 new file mode 100644 index 00000000000..42b1d76cc38 --- /dev/null +++ b/test/powershell/Modules/Microsoft.PowerShell.Management/Clipboard.Tests.ps1 @@ -0,0 +1,45 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +Describe 'Clipboard cmdlet tests' -Tag CI { + BeforeAll { + $xclip = Get-Command xclip -CommandType Application -ErrorAction Ignore + } + + Context 'Text' { + BeforeAll { + $defaultParamValues = $PSdefaultParameterValues.Clone() + $PSDefaultParameterValues["it:skip"] = ($IsWindows -and $env:PROCESSOR_ARCHITECTURE.Contains("arm")) -or ($IsLinux -and $xclip -eq $null) + } + + AfterAll { + $PSDefaultParameterValues = $defaultParamValues + } + + It 'Get-Clipboard returns what is in Set-Clipboard' { + $guid = New-Guid + Set-Clipboard -Value $guid + Get-Clipboard | Should -BeExactly $guid + } + + It 'Get-Clipboard returns an array' { + 1,2 | Set-Clipboard + $out = Get-Clipboard + $out.Count | Should -Be 2 + $out[0] | Should -Be 1 + $out[1] | Should -Be 2 + } + + It 'Get-Clipboard -Raw returns one item' { + 1,2 | Set-Clipboard + (Get-Clipboard -Raw).Count | Should -Be 1 + Get-Clipboard -Raw | Should -BeExactly "1$([Environment]::NewLine)2" + } + + It 'Set-Clipboard -Append will add text' { + 'hello' | Set-Clipboard + 'world' | Set-Clipboard -Append + Get-Clipboard -Raw | Should -BeExactly "hello$([Environment]::NewLine)world" + } + } +} diff --git a/test/powershell/engine/Basic/DefaultCommands.Tests.ps1 b/test/powershell/engine/Basic/DefaultCommands.Tests.ps1 index 8c976378fb0..27980fcef48 100644 --- a/test/powershell/engine/Basic/DefaultCommands.Tests.ps1 +++ b/test/powershell/engine/Basic/DefaultCommands.Tests.ps1 @@ -68,7 +68,7 @@ Describe "Verify approved aliases list" -Tags "CI" { "Alias", "gal", "Get-Alias", $($FullCLR -or $CoreWindows -or $CoreUnix), "ReadOnly", "", "" "Alias", "gbp", "Get-PSBreakpoint", $($FullCLR -or $CoreWindows -or $CoreUnix), "ReadOnly", "", "" "Alias", "gc", "Get-Content", $($FullCLR -or $CoreWindows -or $CoreUnix), "ReadOnly", "", "" -"Alias", "gcb", "Get-Clipboard", $($FullCLR ), "ReadOnly", "", "" +"Alias", "gcb", "Get-Clipboard", $($FullCLR -or $CoreWindows -or $CoreUnix), "", "", "" "Alias", "gci", "Get-ChildItem", $($FullCLR -or $CoreWindows -or $CoreUnix), "ReadOnly", "", "" "Alias", "gcm", "Get-Command", $($FullCLR -or $CoreWindows -or $CoreUnix), "ReadOnly", "", "" "Alias", "gcs", "Get-PSCallStack", $($FullCLR -or $CoreWindows -or $CoreUnix), "ReadOnly", "", "" @@ -158,7 +158,7 @@ Describe "Verify approved aliases list" -Tags "CI" { "Alias", "sasv", "Start-Service", $($FullCLR -or $CoreWindows ), "ReadOnly", "", "" "Alias", "sbp", "Set-PSBreakpoint", $($FullCLR -or $CoreWindows -or $CoreUnix), "ReadOnly", "", "" "Alias", "sc", "Set-Content", $($FullCLR ), "ReadOnly", "", "" -"Alias", "scb", "Set-Clipboard", $($FullCLR ), "ReadOnly", "", "" +"Alias", "scb", "Set-Clipboard", $($FullCLR -or $CoreWindows -or $CoreUnix), "", "", "" "Alias", "select", "Select-Object", $($FullCLR -or $CoreWindows -or $CoreUnix), "ReadOnly", "AllScope", "" "Alias", "set", "Set-Variable", $($FullCLR -or $CoreWindows -or $CoreUnix), "", "", "" "Alias", "shcm", "Show-Command", $($FullCLR -or $CoreWindows ), "ReadOnly", "", "" @@ -259,7 +259,7 @@ Describe "Verify approved aliases list" -Tags "CI" { "Cmdlet", "Get-Alias", "", $($FullCLR -or $CoreWindows -or $CoreUnix), "", "", "None" "Cmdlet", "Get-AuthenticodeSignature", "", $($FullCLR -or $CoreWindows ), "", "", "None" "Cmdlet", "Get-ChildItem", "", $($FullCLR -or $CoreWindows -or $CoreUnix), "", "", "None" -"Cmdlet", "Get-Clipboard", "", $($FullCLR ), "", "", "" +"Cmdlet", "Get-Clipboard", "", $($FullCLR -or $CoreWindows -or $CoreUnix), "", "", "None" "Cmdlet", "Get-CmsMessage", "", $($FullCLR -or $CoreWindows ), "", "", "None" "Cmdlet", "Get-Command", "", $($FullCLR -or $CoreWindows -or $CoreUnix), "", "", "None" "Cmdlet", "Get-ComputerInfo", "", $($FullCLR -or $CoreWindows ), "", "", "None" @@ -420,7 +420,7 @@ Describe "Verify approved aliases list" -Tags "CI" { "Cmdlet", "Set-Acl", "", $($FullCLR -or $CoreWindows ), "", "", "Medium" "Cmdlet", "Set-Alias", "", $($FullCLR -or $CoreWindows -or $CoreUnix), "", "", "Medium" "Cmdlet", "Set-AuthenticodeSignature", "", $($FullCLR -or $CoreWindows ), "", "", "Medium" -"Cmdlet", "Set-Clipboard", "", $($FullCLR ), "", "", "" +"Cmdlet", "Set-Clipboard", "", $($FullCLR -or $CoreWindows -or $CoreUnix), "", "", "Medium" "Cmdlet", "Set-Content", "", $($FullCLR -or $CoreWindows -or $CoreUnix), "", "", "Medium" "Cmdlet", "Set-Date", "", $($FullCLR -or $CoreWindows -or $CoreUnix), "", "", "Medium" "Cmdlet", "Set-ExecutionPolicy", "", $($FullCLR -or $CoreWindows -or $CoreUnix), "", "", "Medium" diff --git a/test/powershell/engine/Help/assets/HelpURI/V3Cmdlets.csv b/test/powershell/engine/Help/assets/HelpURI/V3Cmdlets.csv index b42894f83be..107b919a20d 100644 --- a/test/powershell/engine/Help/assets/HelpURI/V3Cmdlets.csv +++ b/test/powershell/engine/Help/assets/HelpURI/V3Cmdlets.csv @@ -67,8 +67,8 @@ New-TemporaryFile,https://go.microsoft.com/fwlink/?LinkId=821836 New-Guid,https://go.microsoft.com/fwlink/?LinkId=526920 Format-Hex,https://go.microsoft.com/fwlink/?LinkId=526919 Convert-String,https://go.microsoft.com/fwlink/?LinkId=528577 -Get-Clipboard,https://go.microsoft.com/fwlink/?LinkId=526219 -Set-Clipboard,https://go.microsoft.com/fwlink/?LinkId=526220 +Get-Clipboard,https://go.microsoft.com/fwlink/?LinkId=2109905 +Set-Clipboard,https://go.microsoft.com/fwlink/?LinkId=2109826 Write-Information,https://go.microsoft.com/fwlink/?LinkId=525909 Add-LocalGroupMember,https://go.microsoft.com/fwlink/?LinkId=717987 Disable-LocalUser,https://go.microsoft.com/fwlink/?LinkId=717986