Paste the unpastable: how to paste in RDP sessions that don't allow pasting
NOTE: This solution works, but has its quirks. Enjoy with care!
Some companies, notably banks, put security on top priority. There is nothing wrong about it. I even support it. However, if taken too far, security becomes an unnecessary burden.
Setting the stage
You are connected via RDP to a VM, aka secure environment. Copy and paste is disabled. The secure environment has barely anything installed. Best editor is notepad. Due to a “migration to a new platform”™, you need to run some random commands for the services installed on that VM.
But I don’t want to transcribe a complete migration script from one editor into a different one by hand.
Automating it
Luckily there is hammerspoon. I use hammerspoon to automate tasks or key combinations on my MacBook. It turns out that it can also emulate key presses, leading to the following approach:
- Get the content of the clipboard
- Create a function to map the text to key presses
- Bind the function to a key combination
How does that look like?
The keymap:
-- https://github.com/Hammerspoon/hammerspoon/blob/master/extensions/keycodes/keycodes.lua#L67
keyMap = {
["0"] = { {}, "0" },
[" "] = { {}, hs.keycodes.map.space },
["1"] = { {}, "1" },
["2"] = { {}, "2" },
["3"] = { {}, "3" },
["4"] = { {}, "4" },
["5"] = { {}, "5" },
["6"] = { {}, "6" },
["7"] = { {}, "7" },
["8"] = { {}, "8" },
["9"] = { {}, "9" },
["a"] = { {}, "a" },
["b"] = { {}, "b" },
["\\"] = { {}, "\\" },
["c"] = { {}, "c" },
[","] = { {}, "," },
["d"] = { {}, "d" },
["e"] = { {}, "e" },
["="] = { {}, "=" },
["f"] = { {}, "f" },
["g"] = { {}, "g" },
["`"] = { {}, "`" },
["h"] = { {}, "h" },
["i"] = { {}, "i" },
["j"] = { {}, "j" },
["k"] = { {}, "k" },
["l"] = { {}, "l" },
["["] = { {}, "[" },
["m"] = { {}, "m" },
["-"] = { {}, "-" },
["n"] = { {}, "n" },
["o"] = { {}, "o" },
["p"] = { {}, "p" },
["."] = { {}, "." },
["q"] = { {}, "q" },
["'"] = { {}, "'" },
["r"] = { {}, "r" },
["]"] = { {}, "]" },
["s"] = { {}, "s" },
[";"] = { {}, ";" },
["/"] = { {}, "/" },
["t"] = { {}, "t" },
["u"] = { {}, "u" },
["v"] = { {}, "v" },
["w"] = { {}, "w" },
["x"] = { {}, "x" },
["y"] = { {}, "y" },
["z"] = { {}, "z" },
[")"] = { {"shift"}, "0" },
["!"] = { {"shift"}, "1" },
["@"] = { {"shift"}, "2" },
["#"] = { {"shift"}, "3" },
["$"] = { {"shift"}, "4" },
["%"] = { {"shift"}, "5" },
["^"] = { {"shift"}, "6" },
["&"] = { {"shift"}, "7" },
["*"] = { {"shift"}, "8" },
["("] = { {"shift"}, "9" },
["A"] = { {"shift"}, "a" },
["B"] = { {"shift"}, "b" },
["|"] = { {"shift"}, "\\" },
["C"] = { {"shift"}, "c" },
["<"] = { {"shift"}, "," },
["D"] = { {"shift"}, "d" },
["E"] = { {"shift"}, "e" },
["+"] = { {"shift"}, "=" },
["F"] = { {"shift"}, "f" },
["G"] = { {"shift"}, "g" },
["~"] = { {"shift"}, "`" },
["H"] = { {"shift"}, "h" },
["I"] = { {"shift"}, "i" },
["J"] = { {"shift"}, "j" },
["K"] = { {"shift"}, "k" },
["L"] = { {"shift"}, "l" },
["{"] = { {"shift"}, "[" },
["M"] = { {"shift"}, "m" },
["_"] = { {"shift"}, "-" },
["N"] = { {"shift"}, "n" },
["O"] = { {"shift"}, "o" },
["P"] = { {"shift"}, "p" },
[">"] = { {"shift"}, "." },
["Q"] = { {"shift"}, "q" },
["\""] = { {"shift"}, "'" },
["R"] = { {"shift"}, "r" },
["}"] = { {"shift"}, "]" },
["S"] = { {"shift"}, "s" },
[":"] = { {"shift"}, ";" },
["?"] = { {"shift"}, "/" },
["T"] = { {"shift"}, "t" },
["U"] = { {"shift"}, "u" },
["V"] = { {"shift"}, "v" },
["W"] = { {"shift"}, "w" },
["X"] = { {"shift"}, "x" },
["Y"] = { {"shift"}, "y" },
["Z"] = { {"shift"}, "z" },
["\n"] = {{}, hs.keycodes.map["return"] },
["\t"] = {{}, hs.keycodes.map["tab"] }
}
Emitting keys:
local function resetModifiers()
hs.eventtap.event.newKeyEvent(hs.keycodes.map.alt, false):post()
hs.eventtap.event.newKeyEvent(hs.keycodes.map.ctrl, false):post()
hs.eventtap.event.newKeyEvent(hs.keycodes.map.cmd, false):post()
end
local function emitKey(mod, c)
-- I had many problems with modifiers, sometimes CTRL was activated,
-- even though not pressed or emitted.
resetModifiers()
if #mod > 0 then
hs.eventtap.event.newKeyEvent(hs.keycodes.map.shift, true):post()
else
hs.eventtap.event.newKeyEvent(hs.keycodes.map.shift, false):post()
end
hs.timer.usleep(20000)
-- print(hs.inspect(hs.eventtap.checkKeyboardModifiers()))
hs.eventtap.event.newKeyEvent(c, true):post()
hs.timer.usleep(40000)
hs.eventtap.event.newKeyEvent(c, false):post()
if #mod > 0 then
hs.timer.usleep(20000)
hs.eventtap.event.newKeyEvent(hs.keycodes.map.shift, false):post()
end
end
Here I was lazy: I don’t check any modifier keys, the presence of at least one modifer key activates shift. Room for improvement, I would say.
The sleep times were determined by experimentation: I was using RDP over a VPN connection, so lag was part of the game. With shorter intervals the key strokes were too fast and the content was not transmitted correctly.
Binding the function to a key combination:
hs.hotkey.bind({}, "f8", nil, function()
hs.timer.doAfter(.6, function()
-- print(hs.inspect(hs.eventtap.checkKeyboardModifiers()))
local res = hs.pasteboard.getContents()
for i = 1, #res do
local c = res:sub(i,i)
local u = keyMap[c]
-- print(string.format("%s >%s<",c,u[2]))
if u ~= nil then
emitKey(u[1], u[2])
hs.timer.usleep(40000)
end
end
end)
end)
I chose F8, because I had problems with combinations based on modifier keys: they seemed to stay activated under certain (to me unknown) conditions (see also a comment on the issue), causing all kinds of window switching, closing and random behaviour.
This solution is not very fancy and for sure can be improved. But it works, serves its purpose and perhaps you can use it also in some way.