Added cross-platform Get-Clipboard and Set-Clipboard (#10340)

Co-Authored-By: Ilya <darpa@yandex.ru>



Co-Authored-By: Ilya <darpa@yandex.ru>
This commit is contained in:
Steve Lee
2019-11-04 11:28:58 -08:00
committed by Travis Plunk
co-authored by Ilya
parent 4b8462766f
commit b74a26bb91
11 changed files with 491 additions and 483 deletions
+4 -3
View File
@@ -1331,9 +1331,6 @@
<Component Id="cmpB2DC1821DBE648078AEA75AC0AB62408" Guid="{4242bbb5-81dd-481d-a46a-6397d5bce4a5}">
<File Id="filA7C18F70F1814A08A63620BC16B1C4DC" KeyPath="yes" Source="$(env.ProductSourcePath)\ref\System.IO.FileSystem.DriveInfo.dll" />
</Component>
<Component Id="cmpD63D95DCAE9747E3A170CDD082EE4432" Guid="{8fb8d745-02e0-4ca3-b570-06981423b367}">
<File Id="filC57EEBC7B79642DF81737183462ABBE4" KeyPath="yes" Source="$(env.ProductSourcePath)\ref\System.Drawing.dll" />
</Component>
<Component Id="cmp9C1983722FB842AEAAE7FE004439F7F5" Guid="{28fac38a-2d64-4651-b17d-822f689ad00f}">
<File Id="fil3E3F9C4BC1D5493798264C5592B2D26B" KeyPath="yes" Source="$(env.ProductSourcePath)\ref\System.Security.Principal.dll" />
</Component>
@@ -1643,6 +1640,9 @@
<Component Id="cmpF1B0E6DCC3BD4797B206C879EF270062" Guid="{af38a3e7-6bc2-47c8-a83e-ee8bfd0cc8be}">
<File Id="fil2694AE6F8EB8473CBDF0F73A4E55164B" KeyPath="yes" Source="$(env.ProductSourcePath)\ref\System.Text.Encoding.CodePages.dll" />
</Component>
<Component Id="cmp25A144CC34E545378735B06794A02F90" Guid="{35a3c141-1309-4277-9325-a97e177a9f09}">
<File Id="filFCE4A935DE1D4DD08E9A1D32A07E0266" KeyPath="yes" Source="$(env.ProductSourcePath)\ref\System.Drawing.dll" />
</Component>
</Directory>
<Directory Id="dirC72EEAF9434D4AD066E0F4D20D8E816C" Name="en-US">
<Component Id="cmpDC2EF4541FA5A2E63F36A8CD6C5FA51B" Guid="{CE8DDB7C-A16E-4A1E-80D5-9937D07A1723}">
@@ -3919,6 +3919,7 @@
<ComponentRef Id="cmp73A4CC8E2C9E4594A3D8258C9E00D7E6" />
<ComponentRef Id="cmpAC984FA3A67247BC996F60290C87D0A1" />
<ComponentRef Id="cmp2E32A6E773F543F0A150B972F8D3E23A" />
<ComponentRef Id="cmp25A144CC34E545378735B06794A02F90" />
</ComponentGroup>
</Fragment>
</Wix>
@@ -18,14 +18,12 @@
<Compile Remove="commands\management\ControlPanelItemCommand.cs" />
<Compile Remove="commands\management\CommitTransactionCommand.cs" />
<Compile Remove="commands\management\Eventlog.cs" />
<Compile Remove="commands\management\GetClipboardCommand.cs" />
<Compile Remove="commands\management\GetTransactionCommand.cs" />
<Compile Remove="commands\management\GetWMIObjectCommand.cs" />
<Compile Remove="commands\management\InvokeWMIMethodCommand.cs" />
<Compile Remove="commands\management\RegisterWMIEventCommand.cs" />
<Compile Remove="commands\management\RemoveWMIObjectCommand.cs" />
<Compile Remove="commands\management\RollbackTransactionCommand.cs" />
<Compile Remove="commands\management\SetClipboardCommand.cs" />
<Compile Remove="commands\management\SetWMIInstanceCommand.cs" />
<Compile Remove="commands\management\StartTransactionCommand.cs" />
<Compile Remove="commands\management\UseTransactionCommand.cs" />
@@ -38,7 +36,6 @@
<Compile Remove="gen\ControlPanelResources.cs" />
<Compile Remove="gen\WmiResources.cs" />
<Compile Remove="gen\ManagementMshSnapInResources.cs" />
<Compile Remove="gen\ClipboardResources.cs" />
<EmbeddedResource Remove="resources\EventlogResources.resx" />
<EmbeddedResource Remove="resources\TransactionResources.resx" />
@@ -46,7 +43,6 @@
<EmbeddedResource Remove="resources\ControlPanelResources.resx" />
<EmbeddedResource Remove="resources\WmiResources.resx" />
<EmbeddedResource Remove="resources\ManagementMshSnapInResources.resx" />
<EmbeddedResource Remove="resources\ClipboardResources.resx" />
</ItemGroup>
<ItemGroup>
@@ -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<string, uint>[] 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<bool> 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;
}
}
}
}
@@ -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
{
/// <summary>
/// Defines the different type supported by the clipboard.
/// </summary>
public enum ClipboardFormat
{
/// Text format as default.
Text = 0,
/// File format.
FileDropList = 1,
/// Image format.
Image = 2,
/// Audio format.
Audio = 3,
};
/// <summary>
/// Defines the implementation of the 'Get-Clipboard' cmdlet.
/// This cmdlet get the content from system clipboard.
/// </summary>
[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
{
/// <summary>
/// Property that sets clipboard type. This will return the required format from clipboard.
/// </summary>
[Parameter]
public ClipboardFormat Format { get; set; }
/// <summary>
/// Property that sets format type when the return type is text.
/// </summary>
[Parameter]
[ValidateNotNullOrEmpty]
public TextDataFormat TextFormatType
{
get { return _textFormat; }
set
{
_isTextFormatTypeSet = true;
_textFormat = value;
}
}
private TextDataFormat _textFormat = TextDataFormat.UnicodeText;
private bool _isTextFormatTypeSet = false;
/// <summary>
/// Property that sets raw parameter. This will allow clipboard return text or file list as one string.
/// </summary>
@@ -79,76 +27,38 @@ namespace Microsoft.PowerShell.Commands
set
{
_isRawSet = true;
_raw = value;
}
}
private bool _raw;
private bool _isRawSet = false;
/// <summary>
/// This method implements the ProcessRecord method for Get-Clipboard command.
/// </summary>
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);
}
/// <summary>
/// Returns the clipboard content as text format.
/// </summary>
/// <param name="textFormat"></param>
/// <returns></returns>
private List<string> GetClipboardContentAsText(TextDataFormat textFormat)
/// <returns>Array of strings representing content from clipboard.</returns>
private List<string> GetClipboardContentAsText()
{
if (!Clipboard.ContainsText(textFormat))
var result = new List<string>();
string textContent = null;
try
{
return null;
textContent = Clipboard.GetText();
}
catch (PlatformNotSupportedException)
{
ThrowTerminatingError(new ErrorRecord(new InvalidOperationException(ClipboardResources.UnsupportedPlatform), "FailedToGetClipboardUnsupportedPlatform", ErrorCategory.InvalidOperation, "Clipboard"));
}
List<string> result = new List<string>();
// 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 @@ namespace Microsoft.PowerShell.Commands
return result;
}
/// <summary>
/// Returns the clipboard content as file info.
/// </summary>
/// <returns></returns>
private List<PSObject> GetClipboardContentAsFileList()
{
if (!Clipboard.ContainsFileDropList())
{
return null;
}
List<PSObject> result = new List<PSObject>();
foreach (string filePath in Clipboard.GetFileDropList())
{
FileInfo file = new FileInfo(filePath);
result.Add(WrapOutputInPSObject(file, filePath));
}
return result;
}
/// <summary>
/// Wraps the item in a PSObject and attaches some notes to the
/// object that deal with path information.
/// </summary>
/// <param name="item"></param>
/// <param name="path"></param>
/// <returns></returns>
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;
}
}
}
@@ -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.
/// </summary>
[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<string> _contentList = new List<string>();
private const string ValueParameterSet = "Value";
private const string PathParameterSet = "Path";
private const string LiteralPathParameterSet = "LiteralPath";
/// <summary>
/// Property that sets clipboard content.
/// </summary>
[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 @@ namespace Microsoft.PowerShell.Commands
[Parameter]
public SwitchParameter Append { get; set; }
/// <summary>
/// Property that sets Path parameter. This will allow to set file formats to Clipboard.
/// </summary>
[Parameter(ParameterSetName = PathParameterSet, Mandatory = true, ValueFromPipelineByPropertyName = true)]
[ValidateNotNullOrEmpty]
[SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")]
public string[] Path { get; set; }
/// <summary>
/// Property that sets LiteralPath parameter. This will allow to set file formats to Clipboard.
/// </summary>
[Parameter(ParameterSetName = LiteralPathParameterSet, Mandatory = true, ValueFromPipelineByPropertyName = true)]
[Alias("PSPath")]
[ValidateNotNullOrEmpty]
[SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")]
public string[] LiteralPath { get; set; }
/// <summary>
/// Property that sets html parameter. This will allow html content rendered as html to clipboard.
/// </summary>
[Parameter]
public SwitchParameter AsHtml
{
get { return _asHtml; }
set
{
_isHtmlSet = true;
_asHtml = value;
}
}
private bool _asHtml;
private bool _isHtmlSet = false;
/// <summary>
/// This method implements the BeginProcessing method for Set-Clipboard command.
/// </summary>
@@ -94,26 +50,10 @@ namespace Microsoft.PowerShell.Commands
/// </summary>
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);
}
}
/// <summary>
@@ -121,27 +61,15 @@ namespace Microsoft.PowerShell.Commands
/// </summary>
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);
}
/// <summary>
/// Set the clipboard content.
/// </summary>
/// <param name="contentList"></param>
/// <param name="append"></param>
/// <param name="asHtml"></param>
private void SetClipboardContent(List<string> contentList, bool append, bool asHtml)
/// <param name="contentList">The content to store into the clipboard.</param>
/// <param name="append">If true, appends to clipboard instead of overwriting.</param>
private void SetClipboardContent(List<string> contentList, bool append)
{
string setClipboardShouldProcessTarget;
@@ -150,7 +78,7 @@ namespace Microsoft.PowerShell.Commands
setClipboardShouldProcessTarget = string.Format(CultureInfo.InvariantCulture, ClipboardResources.ClipboardCleared);
if (ShouldProcess(setClipboardShouldProcessTarget, "Set-Clipboard"))
{
Clipboard.Clear();
Clipboard.SetText(string.Empty);
}
return;
@@ -159,15 +87,7 @@ namespace Microsoft.PowerShell.Commands
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 @@ namespace Microsoft.PowerShell.Commands
content.Append(string.Join(Environment.NewLine, contentList.ToArray(), 0, contentList.Count));
}
// Verbose output
string verboseString = null;
if (contentList != null)
{
@@ -198,230 +117,8 @@ namespace Microsoft.PowerShell.Commands
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());
Clipboard.SetText(content.ToString());
}
}
/// <summary>
/// Copy the file format to clipboard.
/// </summary>
/// <param name="fileList"></param>
/// <param name="append"></param>
/// <param name="isLiteralPath"></param>
private void CopyFilesToClipboard(List<string> fileList, bool append, bool isLiteralPath)
{
int clipBoardContentLength = 0;
HashSet<string> dropFiles = new HashSet<string>(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<string>(clipBoardContent.Cast<string>().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<string> newPaths = new Collection<string>();
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<string>(dropFiles.Count - 1));
}
else
{
setClipboardShouldProcessTarget = string.Format(CultureInfo.InvariantCulture, ClipboardResources.SetSingleFileToClipboard, dropFiles.ElementAt<string>(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);
}
}
/// <summary>
/// Generate HTML fragment data string with header that is required for the clipboard.
/// </summary>
/// <param name="html">The html to generate for.</param>
/// <returns>The resulted string.</returns>
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 = "<!--StartFragment-->";
const string EndFragment = @"<!--EndFragment-->";
var sb = new StringBuilder();
sb.AppendLine(Header);
sb.AppendLine(@"<!DOCTYPE HTML PUBLIC ""-//W3C//DTD HTML 4.0 Transitional//EN"">");
// 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 "<html", ignore white space and case
int htmlOpenIdx = Regex.Match(html, @"<\s*h\s*t\s*m\s*l", RegexOptions.IgnoreCase).Index;
int htmlOpenEndIdx = htmlOpenIdx > 0 ? html.IndexOf('>', htmlOpenIdx) + 1 : -1;
// find the index of "</html", ignore white space and case
int htmlCloseIdx = Regex.Match(html, @"<\s*/\s*h\s*t\s*m\s*l", RegexOptions.IgnoreCase).Index;
if (fragmentStartIdx < 0 && fragmentEndIdx < 0)
{
// find the index of "<body", ignore white space and case
int bodyOpenIdx = Regex.Match(html, @"<\s*b\s*o\s*d\s*y", RegexOptions.IgnoreCase).Index;
int bodyOpenEndIdx = bodyOpenIdx > 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("<html><body>");
sb.Append(StartFragment);
fragmentStart = GetByteCount(sb);
sb.Append(html);
fragmentEnd = GetByteCount(sb);
sb.Append(EndFragment);
sb.Append("</body></html>");
}
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 "</body", ignore white space and case
int bodyCloseIdx = Regex.Match(html, @"<\s*/\s*b\s*o\s*d\s*y", RegexOptions.IgnoreCase).Index;
if (htmlOpenEndIdx < 0)
sb.Append("<html>");
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("</html>");
}
}
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();
}
/// <summary>
/// Calculates the number of bytes produced by encoding the string in the string builder in UTF-8 and not .NET default string encoding.
/// </summary>
/// <param name="sb">The string builder to count its string.</param>
/// <param name="start">Optional: the start index to calculate from (default - start of string).</param>
/// <param name="end">Optional: the end index to calculate to (default - end of string).</param>
/// <returns>The number of bytes required to encode the string in UTF-8.</returns>
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;
}
}
}
@@ -131,4 +131,22 @@
<data name="InvalidHtmlCombine" xml:space="preserve">
<value>Html can only be combined with Html Text format.</value>
</data>
<data name="UnsupportedFormat" xml:space="preserve">
<value>Only Text format is supported on this platform.</value>
</data>
<data name="UnsupportedPlatform" xml:space="preserve">
<value>The clipboard is not supported on this platform.</value>
</data>
<data name="AsHtmlUnsupported" xml:space="preserve">
<value>The '-AsHtml' switch is not supported on this platform.</value>
</data>
<data name="TextFormatUnsupported" xml:space="preserve">
<value>The '-TextFormatType' parameter only supports 'Text' on this platform.</value>
</data>
<data name="PathUnsupported" xml:space="preserve">
<value>The '-Path' parameter is not supported on this platform.</value>
</data>
<data name="LiteralPathUnsupported" xml:space="preserve">
<value>The '-LiteralPath' parameter is not supported on this platform.</value>
</data>
</root>
@@ -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",
@@ -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",
@@ -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"
}
}
}
@@ -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"
@@ -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
1 TopicTitle HelpURI
67 New-Guid https://go.microsoft.com/fwlink/?LinkId=526920
68 Format-Hex https://go.microsoft.com/fwlink/?LinkId=526919
69 Convert-String https://go.microsoft.com/fwlink/?LinkId=528577
70 Get-Clipboard https://go.microsoft.com/fwlink/?LinkId=526219 https://go.microsoft.com/fwlink/?LinkId=2109905
71 Set-Clipboard https://go.microsoft.com/fwlink/?LinkId=526220 https://go.microsoft.com/fwlink/?LinkId=2109826
72 Write-Information https://go.microsoft.com/fwlink/?LinkId=525909
73 Add-LocalGroupMember https://go.microsoft.com/fwlink/?LinkId=717987
74 Disable-LocalUser https://go.microsoft.com/fwlink/?LinkId=717986