mirror of
https://github.com/mRemoteNG/mRemoteNG.git
synced 2026-02-20 07:39:27 +08:00
Compare commits
898 Commits
v1.78.1-de
...
v1.78.2-de
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3a2576384b | ||
|
|
6d35121a85 | ||
|
|
44651c9d4c | ||
|
|
1222c87760 | ||
|
|
acfb035027 | ||
|
|
93f6d017e6 | ||
|
|
455d897b9f | ||
|
|
3d4d1136f0 | ||
|
|
598136f089 | ||
|
|
865bddfb04 | ||
|
|
3b2bc028cc | ||
|
|
71c746f801 | ||
|
|
079814751e | ||
|
|
1de9ce6ef5 | ||
|
|
01df0e295e | ||
|
|
2631926eda | ||
|
|
db1496d4a2 | ||
|
|
a8b12c9ba1 | ||
|
|
a2b408e537 | ||
|
|
a871624ab7 | ||
|
|
e40a800bc4 | ||
|
|
48cb1ce770 | ||
|
|
6733d758aa | ||
|
|
f78b9bf51c | ||
|
|
213ea6a4d3 | ||
|
|
ac3d7e6366 | ||
|
|
9fae2e066e | ||
|
|
cf66e84d31 | ||
|
|
f7326aff62 | ||
|
|
d4bca6b03d | ||
|
|
1d86015f9d | ||
|
|
5efcc653eb | ||
|
|
333588e101 | ||
|
|
4046681fc5 | ||
|
|
91c7df22b2 | ||
|
|
173b208eb1 | ||
|
|
50de37c3a4 | ||
|
|
380e91de07 | ||
|
|
ba97933f33 | ||
|
|
539b761199 | ||
|
|
aaff6e4548 | ||
|
|
a1e3b34580 | ||
|
|
276585e379 | ||
|
|
9242dc0faf | ||
|
|
14d08d8d62 | ||
|
|
421d8eb581 | ||
|
|
aed0006f1d | ||
|
|
86d986a633 | ||
|
|
5163aeb4d2 | ||
|
|
a103939c64 | ||
|
|
bcb8e05698 | ||
|
|
2e74313f07 | ||
|
|
c9791454ec | ||
|
|
b1c1696acb | ||
|
|
ab668ac677 | ||
|
|
1777c4840a | ||
|
|
90fcd672d8 | ||
|
|
4226396cbf | ||
|
|
3591ca0f4c | ||
|
|
09cbcccf30 | ||
|
|
4e36b5666e | ||
|
|
308253a325 | ||
|
|
30bb4016b4 | ||
|
|
e616ae16e1 | ||
|
|
469528a07a | ||
|
|
c7f831e9f9 | ||
|
|
dd9922be45 | ||
|
|
d0a468c22b | ||
|
|
ff5dbc88fe | ||
|
|
3a4ae9b098 | ||
|
|
2de24e534c | ||
|
|
59412a65e1 | ||
|
|
adedb6962f | ||
|
|
d3fa608ae9 | ||
|
|
3159903875 | ||
|
|
31c28c4917 | ||
|
|
2c13f7c3a7 | ||
|
|
725ee92147 | ||
|
|
14406f79a2 | ||
|
|
3e202c3a19 | ||
|
|
412c727e4c | ||
|
|
31e7b9e443 | ||
|
|
a18e292765 | ||
|
|
258ea87f90 | ||
|
|
fd9eabe1e6 | ||
|
|
a9dd06df45 | ||
|
|
dfc24b0cb2 | ||
|
|
63f5325f29 | ||
|
|
6f7949214b | ||
|
|
6cfec060a0 | ||
|
|
db733424ca | ||
|
|
49eab4d377 | ||
|
|
53e5396031 | ||
|
|
99d01130bf | ||
|
|
62ce6cd6e7 | ||
|
|
35c66b0e4a | ||
|
|
dafc05dc42 | ||
|
|
458a05ea5f | ||
|
|
38acc1e960 | ||
|
|
f83209a2b9 | ||
|
|
9d1546c8b7 | ||
|
|
dca2517cf0 | ||
|
|
ff54ca9015 | ||
|
|
76cb0a1e0b | ||
|
|
c683854678 | ||
|
|
e9d0a8aa69 | ||
|
|
30cb1de711 | ||
|
|
699b93e175 | ||
|
|
c7df6f3715 | ||
|
|
f1d1a19779 | ||
|
|
1c2b5fadc4 | ||
|
|
97f5daa13d | ||
|
|
63e100c233 | ||
|
|
92a710e72a | ||
|
|
fe82146f47 | ||
|
|
64b0496ef7 | ||
|
|
7d21b3de9e | ||
|
|
68a8caea34 | ||
|
|
aee18bae3b | ||
|
|
ff58cae677 | ||
|
|
548f938757 | ||
|
|
b9c2e82af1 | ||
|
|
23bb5ec7be | ||
|
|
0b7b87d84a | ||
|
|
b84a49e99c | ||
|
|
327cc46c8d | ||
|
|
8540434594 | ||
|
|
41536ba971 | ||
|
|
8ddaf024ff | ||
|
|
7e8f647f54 | ||
|
|
9a32a7c68c | ||
|
|
280a930792 | ||
|
|
2540d39dd8 | ||
|
|
2b00fa0ad9 | ||
|
|
810aad90ed | ||
|
|
59c8968d8a | ||
|
|
4bfed87d81 | ||
|
|
5c36477b1f | ||
|
|
a529b978e1 | ||
|
|
eae83eadab | ||
|
|
297260af9a | ||
|
|
f5d38c282f | ||
|
|
493e5eda6a | ||
|
|
88b8201384 | ||
|
|
b88204c261 | ||
|
|
d39fb10b47 | ||
|
|
96e7313b69 | ||
|
|
02be546c5b | ||
|
|
c5b98c6cc8 | ||
|
|
687a4088ab | ||
|
|
8c63843c22 | ||
|
|
469ca48592 | ||
|
|
208ce663b2 | ||
|
|
843243c75e | ||
|
|
b7ed5a300d | ||
|
|
415a649a76 | ||
|
|
462d9c39af | ||
|
|
d52d79dd69 | ||
|
|
5dbc6e74e0 | ||
|
|
986b0084bf | ||
|
|
78bdf02c14 | ||
|
|
b4ea8f00ad | ||
|
|
7fd992e9f1 | ||
|
|
76e739cdc1 | ||
|
|
0e0cc1c884 | ||
|
|
21fba7f371 | ||
|
|
3f3a0c3c21 | ||
|
|
1db87b18de | ||
|
|
44b01bca96 | ||
|
|
441b13bed6 | ||
|
|
dd72e9edb7 | ||
|
|
8bbcc9b877 | ||
|
|
98580a8dc6 | ||
|
|
3600905a39 | ||
|
|
c207685b91 | ||
|
|
683f3bcb80 | ||
|
|
23f964ba28 | ||
|
|
56271ba346 | ||
|
|
252a402f4f | ||
|
|
d2d0722ac2 | ||
|
|
ce9bdccc4d | ||
|
|
2a992aa1d8 | ||
|
|
f73729bbe8 | ||
|
|
8634edbd8c | ||
|
|
cc3ead0799 | ||
|
|
566cd26e73 | ||
|
|
2354d5a8a4 | ||
|
|
778067a590 | ||
|
|
d8d9c844d1 | ||
|
|
dc6655514a | ||
|
|
feb3b72fd0 | ||
|
|
479e18b204 | ||
|
|
b1c169d0b8 | ||
|
|
7f75fadb31 | ||
|
|
6af70543ea | ||
|
|
59d5d575ad | ||
|
|
05f2b84cf5 | ||
|
|
a39d4a3063 | ||
|
|
1da019575e | ||
|
|
195c4be4e2 | ||
|
|
4dd672e600 | ||
|
|
f3a14c5ede | ||
|
|
af696a96aa | ||
|
|
d7de52fb4c | ||
|
|
ae66a8fcfc | ||
|
|
56d18774a8 | ||
|
|
7ba22c5555 | ||
|
|
dab7715eb7 | ||
|
|
ec8bac933d | ||
|
|
958ec65b17 | ||
|
|
e775acc209 | ||
|
|
d23631faef | ||
|
|
518401b2c4 | ||
|
|
20c3ebd662 | ||
|
|
8d0d03e152 | ||
|
|
632cfca71f | ||
|
|
221ff38a66 | ||
|
|
3ac7365c9b | ||
|
|
be53755010 | ||
|
|
30eafa3efa | ||
|
|
7c0a6a51d4 | ||
|
|
181f96823e | ||
|
|
7b4e9781ac | ||
|
|
7a4ff98ba8 | ||
|
|
100c2e3078 | ||
|
|
cc6f07d943 | ||
|
|
cc3a9c3a00 | ||
|
|
ce1d82c730 | ||
|
|
ec3a01de70 | ||
|
|
af894964fc | ||
|
|
5cebc4d418 | ||
|
|
04e0144004 | ||
|
|
c33c9814c2 | ||
|
|
3a946e5810 | ||
|
|
6a6a894a1c | ||
|
|
e1ffb9262d | ||
|
|
b3eb6904f9 | ||
|
|
72cbade402 | ||
|
|
6fd008c2e8 | ||
|
|
25f26f08de | ||
|
|
7ebc62d8e3 | ||
|
|
e04ba4f9e0 | ||
|
|
0d4324b009 | ||
|
|
6404956a62 | ||
|
|
769db78ee8 | ||
|
|
8bb8d52b1d | ||
|
|
e990659d05 | ||
|
|
0fcef353ae | ||
|
|
bf91a8a709 | ||
|
|
d81a608b3a | ||
|
|
8413ee77d4 | ||
|
|
4d92a30089 | ||
|
|
57f7e09bb6 | ||
|
|
19fdccc540 | ||
|
|
83c2f2a7a8 | ||
|
|
9dcea7ed6e | ||
|
|
6ad3de91e9 | ||
|
|
2bbbd166f9 | ||
|
|
327ba3f4e1 | ||
|
|
c9b77b6616 | ||
|
|
a94e58c83e | ||
|
|
d40794d7a0 | ||
|
|
7dd916dd80 | ||
|
|
d16d76d0fa | ||
|
|
a896949752 | ||
|
|
f63186a7a6 | ||
|
|
ee88e835ed | ||
|
|
1288a274f3 | ||
|
|
facc523aae | ||
|
|
11dfdd797d | ||
|
|
3dc37d0359 | ||
|
|
b00185e1dc | ||
|
|
44c65775ad | ||
|
|
9c31ee28b6 | ||
|
|
5868f91d2d | ||
|
|
ee3735904d | ||
|
|
2b39dde523 | ||
|
|
611fdcebe9 | ||
|
|
b6c21bb21f | ||
|
|
ed2fee3195 | ||
|
|
2d7a897a4d | ||
|
|
4f14c78b58 | ||
|
|
8b8c1d919e | ||
|
|
e960353de6 | ||
|
|
e563a4c319 | ||
|
|
131aef7069 | ||
|
|
73c3e078d1 | ||
|
|
8858d55970 | ||
|
|
f983f5de02 | ||
|
|
a8efba1ed4 | ||
|
|
8fc3682cc9 | ||
|
|
4ade871aba | ||
|
|
0a381e0a44 | ||
|
|
895e16d4cb | ||
|
|
6657fa3ad0 | ||
|
|
f5fcc7c206 | ||
|
|
435e6f46c1 | ||
|
|
9ef81e6e72 | ||
|
|
e4b4400aa5 | ||
|
|
1d9e082a4a | ||
|
|
43d11a5de2 | ||
|
|
94db6caa2c | ||
|
|
11366e5d8d | ||
|
|
a2967fa999 | ||
|
|
12920bc6c6 | ||
|
|
55fec45b58 | ||
|
|
045aa631e4 | ||
|
|
3cf3a70fc7 | ||
|
|
290dab29f1 | ||
|
|
79b252f839 | ||
|
|
49bd76f197 | ||
|
|
54492bedfb | ||
|
|
2d1c2df2cb | ||
|
|
1187e1c95e | ||
|
|
664a63211e | ||
|
|
b7df1e80c8 | ||
|
|
2d6563022e | ||
|
|
5423c84eed | ||
|
|
d263fb1b80 | ||
|
|
c72bd2a107 | ||
|
|
8e4627c872 | ||
|
|
ccbe99f828 | ||
|
|
ee483e3b2b | ||
|
|
5b4b13828b | ||
|
|
4b0bc71fb0 | ||
|
|
c28ae335b2 | ||
|
|
2d3830b747 | ||
|
|
fe4e205772 | ||
|
|
5e2fc8b0dc | ||
|
|
6403573e36 | ||
|
|
39b863b57e | ||
|
|
8e5cdcb6e5 | ||
|
|
f5b7cd6fb1 | ||
|
|
493ddc1f1f | ||
|
|
5ad338bdcc | ||
|
|
d1a1adb994 | ||
|
|
c879b030ad | ||
|
|
8d1d55c38e | ||
|
|
aef576d290 | ||
|
|
e34b0fded3 | ||
|
|
14e5e6a715 | ||
|
|
0579273964 | ||
|
|
5839a836c8 | ||
|
|
236fff8014 | ||
|
|
c1efefae81 | ||
|
|
1e5bd44332 | ||
|
|
7478537f3e | ||
|
|
b0f50b825f | ||
|
|
7509fcda52 | ||
|
|
0991cf74ef | ||
|
|
61dd2ec8db | ||
|
|
77643848b6 | ||
|
|
21ab172041 | ||
|
|
d6c2c2158a | ||
|
|
0c50e2d78e | ||
|
|
64183e8e7d | ||
|
|
b9595d1650 | ||
|
|
fa3be420b8 | ||
|
|
e2d1929553 | ||
|
|
4f05e8dbe9 | ||
|
|
ea9e79d930 | ||
|
|
e1a8d60cd7 | ||
|
|
4e20049ea3 | ||
|
|
89ccd33fd1 | ||
|
|
4668104e36 | ||
|
|
b4aa30de23 | ||
|
|
526cb1fb47 | ||
|
|
03b395ee15 | ||
|
|
4125344254 | ||
|
|
531e117731 | ||
|
|
588fdca4d7 | ||
|
|
285ca46c18 | ||
|
|
8bdb83eb73 | ||
|
|
d38b71d5ea | ||
|
|
687f06937d | ||
|
|
e0799a6772 | ||
|
|
4943dce1a8 | ||
|
|
8dee5c0d3c | ||
|
|
018b07299e | ||
|
|
6f52b82a6d | ||
|
|
e1ee905286 | ||
|
|
6853b158ce | ||
|
|
d705b8a45d | ||
|
|
cfb707d20f | ||
|
|
3aedc938cb | ||
|
|
c70e4c66e6 | ||
|
|
b727dbd604 | ||
|
|
cd7f237920 | ||
|
|
a1db763060 | ||
|
|
01e22740ce | ||
|
|
6b52b6b062 | ||
|
|
6781fd57af | ||
|
|
8b22c7812d | ||
|
|
80c1391361 | ||
|
|
c77e6b8616 | ||
|
|
e04ace4820 | ||
|
|
d89ea96b34 | ||
|
|
0e83e525fe | ||
|
|
d5d9a8bf03 | ||
|
|
ecb935868d | ||
|
|
d736d3c388 | ||
|
|
2520ccd6be | ||
|
|
4307dd043e | ||
|
|
4ae8e4252b | ||
|
|
995f03d03e | ||
|
|
2965f598ac | ||
|
|
0ad6f5aea1 | ||
|
|
c6fe8ff1f1 | ||
|
|
a3a6917bf8 | ||
|
|
1d8484957e | ||
|
|
0fc3fb4a58 | ||
|
|
6ab67ee844 | ||
|
|
11fa059b1b | ||
|
|
847115abd6 | ||
|
|
a5a0a310ce | ||
|
|
a72582b10f | ||
|
|
929e22dc5a | ||
|
|
ec4bca9bc0 | ||
|
|
0648541ce1 | ||
|
|
77529f29f5 | ||
|
|
02cf1739c0 | ||
|
|
903a5f5799 | ||
|
|
ccd955ca7e | ||
|
|
1af3b03fa8 | ||
|
|
cc28186f73 | ||
|
|
103bc8bd8b | ||
|
|
9bcf7af467 | ||
|
|
8d10980875 | ||
|
|
57d4945d71 | ||
|
|
11febb93bd | ||
|
|
cc9b83b0f8 | ||
|
|
54386924d7 | ||
|
|
35417bc05d | ||
|
|
8b212dd87f | ||
|
|
488035e124 | ||
|
|
9129edb725 | ||
|
|
6f3882e0ca | ||
|
|
f810f6aa85 | ||
|
|
6f13111c4c | ||
|
|
fcca1a1fa3 | ||
|
|
ac0aa9e33b | ||
|
|
6109ff7783 | ||
|
|
4d2d24efb3 | ||
|
|
e2b7081b9e | ||
|
|
6ba7fedc18 | ||
|
|
3973fae8ef | ||
|
|
77752f494f | ||
|
|
71574b62cb | ||
|
|
546ba16366 | ||
|
|
f61ca2ad17 | ||
|
|
781afd1c3b | ||
|
|
2bfd8495ab | ||
|
|
094fa65ad6 | ||
|
|
ec2a652d74 | ||
|
|
1b55645b81 | ||
|
|
5c02581c81 | ||
|
|
62cc4780ca | ||
|
|
8f26d57f40 | ||
|
|
3bd2fe889a | ||
|
|
492a2629c2 | ||
|
|
b64ddf32ff | ||
|
|
e86a550985 | ||
|
|
fcccdacb99 | ||
|
|
487de4c29b | ||
|
|
d36c6cb067 | ||
|
|
a4b704252b | ||
|
|
93e8d26a75 | ||
|
|
0a3ecaac64 | ||
|
|
ea6b762021 | ||
|
|
265a43e31c | ||
|
|
6d156586ac | ||
|
|
68e3f607a3 | ||
|
|
0b240a3902 | ||
|
|
4082761606 | ||
|
|
e68c42ba64 | ||
|
|
347546ee0e | ||
|
|
ca717d6b80 | ||
|
|
5d623d80eb | ||
|
|
a2edbd9934 | ||
|
|
5830f39d50 | ||
|
|
0aa0b59635 | ||
|
|
3c6a485647 | ||
|
|
bbe1fa8416 | ||
|
|
ac4469bb4a | ||
|
|
9e61e8eafa | ||
|
|
b193199268 | ||
|
|
f8b7d37af1 | ||
|
|
b3e9202d72 | ||
|
|
0f819ade56 | ||
|
|
d682afcde2 | ||
|
|
e67754ee9f | ||
|
|
4897771fbf | ||
|
|
4128f3404a | ||
|
|
7bc25ceb38 | ||
|
|
f77f0f5e04 | ||
|
|
0e666efaad | ||
|
|
e2893b9516 | ||
|
|
fb86b13948 | ||
|
|
e22cc6921d | ||
|
|
42fdd91206 | ||
|
|
2329d95002 | ||
|
|
8dda6ba13f | ||
|
|
156e2b8056 | ||
|
|
aa48324b6d | ||
|
|
d2b05ef7c3 | ||
|
|
75545e60b3 | ||
|
|
b3d0b30b56 | ||
|
|
3ed3729fc2 | ||
|
|
cb7ba46be6 | ||
|
|
933b21598e | ||
|
|
7a8442d9ea | ||
|
|
c405186533 | ||
|
|
5d150115a8 | ||
|
|
26bc38cf8c | ||
|
|
8f769cdda3 | ||
|
|
6b35cd3aee | ||
|
|
4ae1281a3a | ||
|
|
9984d4bc8f | ||
|
|
c987ee9fd3 | ||
|
|
a031e6f5f7 | ||
|
|
da386c3119 | ||
|
|
bfd29fc0fc | ||
|
|
9617776be1 | ||
|
|
765f2abc40 | ||
|
|
519bc42e0a | ||
|
|
6537e6bce6 | ||
|
|
494aff1bd3 | ||
|
|
927527b888 | ||
|
|
08054e4873 | ||
|
|
a092fef575 | ||
|
|
2808b3d3da | ||
|
|
ef3b236d73 | ||
|
|
26f0365026 | ||
|
|
25ebfee6e2 | ||
|
|
c1ada03db1 | ||
|
|
0b17360346 | ||
|
|
da18b37a54 | ||
|
|
4cf040c01e | ||
|
|
95b77515dc | ||
|
|
65bbd8ca05 | ||
|
|
dbba954522 | ||
|
|
26d46be243 | ||
|
|
135f8290ec | ||
|
|
a8b4e1178d | ||
|
|
8bdf66ef02 | ||
|
|
5209b08709 | ||
|
|
933247dc2f | ||
|
|
4e43bf9d8f | ||
|
|
0f6a5816db | ||
|
|
8021678995 | ||
|
|
147b5edec4 | ||
|
|
2edbf3222e | ||
|
|
f6bf8c229c | ||
|
|
c30b90af44 | ||
|
|
3b72e7af3d | ||
|
|
37004647eb | ||
|
|
c85cb84e31 | ||
|
|
d504143d7e | ||
|
|
db1c7b9708 | ||
|
|
064cb34705 | ||
|
|
02c442ef99 | ||
|
|
e358cafb3f | ||
|
|
40e447bc05 | ||
|
|
f0bac9ee78 | ||
|
|
a75fbad0ea | ||
|
|
ae5a919421 | ||
|
|
749cb7578a | ||
|
|
622ea20819 | ||
|
|
a9374a5eb1 | ||
|
|
1b78b68e33 | ||
|
|
3e97fe0490 | ||
|
|
2bb34607cc | ||
|
|
fe7fca180e | ||
|
|
0a86b45d59 | ||
|
|
62e0dd365b | ||
|
|
b39c561b72 | ||
|
|
02d099629f | ||
|
|
0f55cd2395 | ||
|
|
119451e4f6 | ||
|
|
0f6d4e5760 | ||
|
|
452ed6a754 | ||
|
|
40928b8ab0 | ||
|
|
f1ed380b42 | ||
|
|
e28ae8f2ba | ||
|
|
d8c6d9d558 | ||
|
|
607123f0be | ||
|
|
0ff72ae665 | ||
|
|
552cee1c24 | ||
|
|
44f05a2968 | ||
|
|
244fbf56d1 | ||
|
|
24845039f1 | ||
|
|
2e5be847ce | ||
|
|
9e947f7a36 | ||
|
|
87a3c60330 | ||
|
|
0d460d543b | ||
|
|
eb535cad2c | ||
|
|
eff00033b0 | ||
|
|
b591f97297 | ||
|
|
9501bb4428 | ||
|
|
4e9757c743 | ||
|
|
8f35b2ef71 | ||
|
|
3782eeeaf1 | ||
|
|
ec78de7a74 | ||
|
|
bd1f311d09 | ||
|
|
31bf36db89 | ||
|
|
a96d344c22 | ||
|
|
dac4988514 | ||
|
|
a1f50f152c | ||
|
|
06a47f4a06 | ||
|
|
f7aa9f7b92 | ||
|
|
894b11704c | ||
|
|
5b1b6ddc73 | ||
|
|
baa5abab2e | ||
|
|
1b2ae29f4a | ||
|
|
99a2b968d5 | ||
|
|
029672a907 | ||
|
|
31ecbaa977 | ||
|
|
6580e1b6db | ||
|
|
16bbe4ccc0 | ||
|
|
6ea3642700 | ||
|
|
b8270d2264 | ||
|
|
7b7fe1b062 | ||
|
|
e67c97cc21 | ||
|
|
1033214658 | ||
|
|
55552ad118 | ||
|
|
552c53b15d | ||
|
|
278fc30ae8 | ||
|
|
80fb676763 | ||
|
|
c511fd1895 | ||
|
|
e43a644eb0 | ||
|
|
70c16caf6b | ||
|
|
21dcfc9435 | ||
|
|
56ff019185 | ||
|
|
44a6cb31aa | ||
|
|
a01ebdaa9d | ||
|
|
651b742cc2 | ||
|
|
e2b1eb6e4a | ||
|
|
2546620c8d | ||
|
|
826f75348c | ||
|
|
b27462da05 | ||
|
|
ad0ade5dd4 | ||
|
|
6e3cf4630e | ||
|
|
6f3768db4b | ||
|
|
98220ccf93 | ||
|
|
52688d7145 | ||
|
|
b24edae66b | ||
|
|
ac39ce26ee | ||
|
|
cf041c661b | ||
|
|
9d173d0bdc | ||
|
|
aafb608149 | ||
|
|
04dd125ee6 | ||
|
|
e501235b2e | ||
|
|
7c6a322787 | ||
|
|
eed729181b | ||
|
|
ce9d42d304 | ||
|
|
733e31e54b | ||
|
|
f1714f3228 | ||
|
|
44c90f0de4 | ||
|
|
8268310685 | ||
|
|
d64eaf6234 | ||
|
|
4d0b848676 | ||
|
|
d508ca33c8 | ||
|
|
86fdea129b | ||
|
|
79af98845a | ||
|
|
2c15c6b0ed | ||
|
|
52ee10efda | ||
|
|
2da7d02a2c | ||
|
|
7af4ba27f3 | ||
|
|
46cd974a58 | ||
|
|
3d791b7f78 | ||
|
|
29e27c1283 | ||
|
|
8434ab460c | ||
|
|
561d073bba | ||
|
|
d46d37acb9 | ||
|
|
4f4d841137 | ||
|
|
b7e80ceee9 | ||
|
|
8a3112c71f | ||
|
|
4276b1439b | ||
|
|
3f626149f4 | ||
|
|
30b674f8f5 | ||
|
|
fe3fb46e94 | ||
|
|
99c524c7b6 | ||
|
|
5da7a582bf | ||
|
|
229c46a0c1 | ||
|
|
78d347889b | ||
|
|
244741ba15 | ||
|
|
015fa601a3 | ||
|
|
1bd9e44ecb | ||
|
|
dc7cfdb762 | ||
|
|
36afbae5af | ||
|
|
89d56a10f0 | ||
|
|
8dcc6031e6 | ||
|
|
b524a5424a | ||
|
|
f06ad99658 | ||
|
|
f4ad61a41b | ||
|
|
3c948aca96 | ||
|
|
57ee792198 | ||
|
|
6d84364cec | ||
|
|
d74fc16cfd | ||
|
|
56d72cb30b | ||
|
|
2f6f03ce8f | ||
|
|
b437ff3dcc | ||
|
|
93d17c2c35 | ||
|
|
b70ea04f02 | ||
|
|
89cde8b518 | ||
|
|
8d78623730 | ||
|
|
4a5b05f669 | ||
|
|
c4f2b7ed2e | ||
|
|
e296c77b14 | ||
|
|
56611c6e41 | ||
|
|
1e216b8cb3 | ||
|
|
302700ac80 | ||
|
|
cc5d943a86 | ||
|
|
7942f93649 | ||
|
|
270c6dd64b | ||
|
|
9f1633bc63 | ||
|
|
8953ec0e1f | ||
|
|
8f5dd3bc93 | ||
|
|
97ee6e6ee0 | ||
|
|
ae626294f3 | ||
|
|
842a45aa17 | ||
|
|
686936e7b2 | ||
|
|
8ac8ad6ed9 | ||
|
|
e7eda49676 | ||
|
|
faae8c7a77 | ||
|
|
5cb67a6e09 | ||
|
|
06747dc38d | ||
|
|
c3813fd87b | ||
|
|
901584a2e4 | ||
|
|
f9e33c7fca | ||
|
|
ea36ca37f0 | ||
|
|
2ea06f3096 | ||
|
|
dc208973c8 | ||
|
|
18ddb1001b | ||
|
|
567bc4a112 | ||
|
|
c5dbf5a412 | ||
|
|
c084e66db5 | ||
|
|
bb6d35f91f | ||
|
|
ac59bc2999 | ||
|
|
7dfb1d395d | ||
|
|
46672ca976 | ||
|
|
94e7e23cda | ||
|
|
2fa0ca41d6 | ||
|
|
2ebe35c6d6 | ||
|
|
354066de0b | ||
|
|
3d3c1f8f23 | ||
|
|
24448d604d | ||
|
|
7d8e530ebf | ||
|
|
5db88040cc | ||
|
|
d9de6e78d7 | ||
|
|
da136bfebd | ||
|
|
6657063441 | ||
|
|
47e6d4d711 | ||
|
|
32c7bd2627 | ||
|
|
3422d064ac | ||
|
|
0b01d40a85 | ||
|
|
7f79dc6098 | ||
|
|
ac7590f23c | ||
|
|
998bd3a1a9 | ||
|
|
0f62f1d72c | ||
|
|
cb1b1a5cac | ||
|
|
63ccdd0a23 | ||
|
|
ead809cc90 | ||
|
|
dfd4851bf1 | ||
|
|
e50f11697f | ||
|
|
4800ad53dc | ||
|
|
9349c3f055 | ||
|
|
d7a82ae911 | ||
|
|
b3354fb033 | ||
|
|
9b56b11c9b | ||
|
|
15247c3637 | ||
|
|
4311d3b057 | ||
|
|
076db0637f | ||
|
|
8a7108910d | ||
|
|
be12600094 | ||
|
|
988305100d | ||
|
|
16ae28ee62 | ||
|
|
30aab57bc9 | ||
|
|
a573914668 | ||
|
|
83771cc2e6 | ||
|
|
d8eaba75d2 | ||
|
|
96f917a1ab | ||
|
|
44d81a6bdf | ||
|
|
1b0e8440ae | ||
|
|
e236dbe661 | ||
|
|
2ba7d09c21 | ||
|
|
2b937689bd | ||
|
|
eeb7eb7ad2 | ||
|
|
c8e29211eb | ||
|
|
57cce7ec22 | ||
|
|
78af721250 | ||
|
|
cba6c2dacc | ||
|
|
b4b12cdbdc | ||
|
|
e7c86c95fe | ||
|
|
f28d96e91a | ||
|
|
b25217dd38 | ||
|
|
f60da8665b | ||
|
|
3d8daff050 | ||
|
|
c41f3c21c1 | ||
|
|
8e7897bf76 | ||
|
|
050bf75fdd | ||
|
|
1bbfcd0a7c | ||
|
|
30106d0006 | ||
|
|
16974068d7 | ||
|
|
08d0f67062 | ||
|
|
2edcb5863c | ||
|
|
44b9e49a54 | ||
|
|
0175903f51 | ||
|
|
0ef282f89c | ||
|
|
b4eec7d44c | ||
|
|
2e040a39db | ||
|
|
7e0ee705ff | ||
|
|
361005e242 | ||
|
|
2ea26f7319 | ||
|
|
44c4ca232f | ||
|
|
f7bb45288e | ||
|
|
c99aa41744 | ||
|
|
4eb1cc939c | ||
|
|
d4c51f18f5 | ||
|
|
af89c8bba7 | ||
|
|
2e951ef0db | ||
|
|
80b82366c4 | ||
|
|
3d75eae9c0 | ||
|
|
0bd8d9c4ca | ||
|
|
b3c1132a25 | ||
|
|
bddcb16e8a | ||
|
|
3cb2ba6fa3 | ||
|
|
205c8914ed | ||
|
|
76e0b67599 | ||
|
|
2c617b230a | ||
|
|
72d3268d39 | ||
|
|
a093a77610 | ||
|
|
a9c6443c18 | ||
|
|
757932ed4a | ||
|
|
55aad85bdd | ||
|
|
952d87f858 | ||
|
|
81fbe68e3b | ||
|
|
c82cd15f24 | ||
|
|
9991f7015f | ||
|
|
e94fac0459 | ||
|
|
816833c9f3 | ||
|
|
a202d72cd6 | ||
|
|
0558b0621a | ||
|
|
6e12b398d4 | ||
|
|
cce9aa5e97 | ||
|
|
6020122297 | ||
|
|
bed11116fe | ||
|
|
66cf93ffdd | ||
|
|
8f1ca7dee2 | ||
|
|
b24eac52ae | ||
|
|
5aaa3a00b4 | ||
|
|
0d737228b0 | ||
|
|
f57c862c3c | ||
|
|
ba476533ed | ||
|
|
28b39688ec | ||
|
|
fd8b1a8807 | ||
|
|
bff44b5d74 | ||
|
|
a9ee32d020 | ||
|
|
4fd1864eef | ||
|
|
3775279659 | ||
|
|
5bba633ab1 | ||
|
|
900cc0afe3 | ||
|
|
29fa64825e | ||
|
|
1c59404299 | ||
|
|
563613be6b | ||
|
|
5ae2296400 | ||
|
|
9534ceccd9 | ||
|
|
878845941d | ||
|
|
a5be2bb5b5 | ||
|
|
19c954f972 | ||
|
|
576f9db387 | ||
|
|
28ff02f8cb | ||
|
|
d4a590d292 | ||
|
|
25f928fc8a | ||
|
|
5dd3d927d6 | ||
|
|
77950632f8 | ||
|
|
e85cee752d | ||
|
|
5c48f13a27 | ||
|
|
18b283db0a | ||
|
|
ca7888a537 | ||
|
|
246e90acd1 | ||
|
|
e58e33974a | ||
|
|
5cb07d003c | ||
|
|
3ecedc8fcf | ||
|
|
a88ed5d3a9 | ||
|
|
37fead2076 | ||
|
|
2770c761b2 | ||
|
|
4b28eb6758 | ||
|
|
ba45b44f45 | ||
|
|
f5186cbadd | ||
|
|
807924c51e | ||
|
|
155d849201 | ||
|
|
e9efd2705c | ||
|
|
204e0d041c | ||
|
|
227d75d956 | ||
|
|
d2706c8748 | ||
|
|
2518b8600f | ||
|
|
9300839d59 |
242
.github/copilot-instructions.md
vendored
Normal file
242
.github/copilot-instructions.md
vendored
Normal file
@@ -0,0 +1,242 @@
|
||||
# GitHub Copilot Instructions for mRemoteNG
|
||||
|
||||
## Project Overview
|
||||
|
||||
mRemoteNG is an open-source, multi-protocol, tabbed remote connections manager for Windows. It's a fork of mRemote that allows users to view and manage all their remote connections (RDP, VNC, SSH, Telnet, HTTP/HTTPS, rlogin, Raw Socket, PowerShell remoting, AnyDesk) in a simple yet powerful interface.
|
||||
|
||||
## Technology Stack
|
||||
|
||||
- **Language**: C# with latest language version
|
||||
- **Framework**: .NET 10.0 for Windows (net10.0-windows10.0.26100.0)
|
||||
- **UI Framework**: Windows Forms with WPF support
|
||||
- **Target Platforms**: x64 and ARM64
|
||||
- **Testing Framework**: NUnit with NSubstitute for mocking
|
||||
- **Build System**: MSBuild with .NET SDK-style projects
|
||||
|
||||
## Building the Project
|
||||
|
||||
### Prerequisites
|
||||
- Visual Studio 2022 (version 17.14.12 or later)
|
||||
- .NET 10.0 Desktop Runtime
|
||||
- Windows 10/11 or Windows Server 2016+
|
||||
|
||||
### Build Commands
|
||||
```powershell
|
||||
# Restore NuGet packages
|
||||
dotnet restore
|
||||
|
||||
# Build the solution
|
||||
msbuild mRemoteNG.sln -p:Configuration=Release -p:Platform=x64
|
||||
|
||||
# Or for ARM64
|
||||
msbuild mRemoteNG.sln -p:Configuration=Release -p:Platform=arm64
|
||||
```
|
||||
|
||||
### Running Tests
|
||||
The project uses NUnit for testing. Test projects are in `mRemoteNGTests/` directory.
|
||||
|
||||
```powershell
|
||||
dotnet test mRemoteNGTests/mRemoteNGTests.csproj
|
||||
```
|
||||
|
||||
## Code Organization
|
||||
|
||||
### Main Projects
|
||||
- **mRemoteNG**: Main application project
|
||||
- **mRemoteNGTests**: Unit and integration tests
|
||||
- **mRemoteNGSpecs**: Specification tests
|
||||
- **ObjectListView.NetCore**: Custom list view control
|
||||
- **ExternalConnectors**: External protocol connector implementations
|
||||
- **mRemoteNGDocumentation**: reStructuredText documentation
|
||||
|
||||
### Key Directories in mRemoteNG Project
|
||||
- `App/`: Application startup and initialization
|
||||
- `Connection/`: Connection models, protocols, and management
|
||||
- `Config/`: Configuration and serialization (XML, CSV)
|
||||
- `Container/`: Container/folder node implementations
|
||||
- `Credential/`: Credential storage and repositories
|
||||
- `Language/`: Localization resource files (.resx)
|
||||
- `Security/`: Authentication, encryption, and password management
|
||||
- `Tree/`: Connection tree UI and management
|
||||
- `UI/`: User interface components and forms
|
||||
- `Tools/`: Utility classes and helpers
|
||||
|
||||
## Code Style and Conventions
|
||||
|
||||
### EditorConfig Settings
|
||||
The project uses EditorConfig (`.editorconfig` in `mRemoteNG/` directory) with these key rules:
|
||||
|
||||
- **Indentation**: 4 spaces (no tabs)
|
||||
- **Line Endings**: CRLF (Windows-style)
|
||||
- **Charset**: UTF-8 with BOM for C# files
|
||||
- **New Lines**: Opening braces on new lines (Allman style)
|
||||
- **Using Directives**: Sort System directives first
|
||||
- **this. Qualifier**: Do not use `this.` prefix unless necessary
|
||||
|
||||
### Naming Conventions
|
||||
- **Classes/Interfaces**: PascalCase (e.g., `ConnectionInfo`, `IInheritable`)
|
||||
- **Methods**: PascalCase (e.g., `GetConnection`, `SaveToXml`)
|
||||
- **Properties**: PascalCase (e.g., `ConnectionName`, `Port`)
|
||||
- **Fields**: Use camelCase for private fields, consider `_` prefix for backing fields
|
||||
- **Constants**: PascalCase
|
||||
|
||||
### Code Patterns
|
||||
|
||||
#### Inheritance System
|
||||
mRemoteNG has a sophisticated property inheritance system for connection settings:
|
||||
|
||||
1. Properties can be inherited from parent containers/folders
|
||||
2. Each inheritable property has a corresponding `Inherit<PropertyName>` boolean in `ConnectionInfoInheritance` class
|
||||
3. Use `IInheritable` interface for objects that support inheritance
|
||||
4. Example: `ConnectionFrameColor` property has `InheritConnectionFrameColor` boolean
|
||||
|
||||
#### Serialization
|
||||
The project supports multiple serialization formats:
|
||||
|
||||
**XML Serialization** (`Config/Serializers/ConnectionSerializers/Xml/`):
|
||||
- Node-based serializers (e.g., `XmlConnectionNodeSerializer28.cs`)
|
||||
- Deserializers handle backward compatibility
|
||||
- Attributes for properties and inheritance flags
|
||||
- Always maintain backward compatibility - old files must still load
|
||||
|
||||
**CSV Serialization** (`Config/Serializers/ConnectionSerializers/Csv/`):
|
||||
- Export connections to CSV format
|
||||
- Include both value and inheritance columns
|
||||
- Maintain column order consistency
|
||||
|
||||
#### Localization
|
||||
All user-facing strings must be localized:
|
||||
|
||||
1. Add entries to `Language/Language.resx` (English base)
|
||||
2. Use `Language.ResourceName` to access strings in code
|
||||
3. Resource naming:
|
||||
- Properties: `PropertyName` (e.g., `ConnectionFrameColor`)
|
||||
- Descriptions: `PropertyDescription<PropertyName>`
|
||||
- Enum values: `<EnumName><Value>` (e.g., `FrameColorRed`)
|
||||
4. Provide clear, concise descriptions for PropertyGrid tooltips
|
||||
|
||||
### Common Patterns
|
||||
|
||||
#### Adding a New Connection Property
|
||||
|
||||
1. **Add enum** (if needed) in `Connection/` directory
|
||||
2. **Add property** to `AbstractConnectionRecord.cs` or `ConnectionInfo.cs`
|
||||
- Add `[Category("CategoryName")]` attribute for PropertyGrid grouping
|
||||
- Add `[Description("Language.PropertyDescription<Name>")]` for tooltip
|
||||
- Use appropriate TypeConverter if needed
|
||||
3. **Add inheritance support** in `ConnectionInfoInheritance.cs`
|
||||
4. **Update serializers**:
|
||||
- XML: Update latest `XmlConnectionNodeSerializer*.cs` and `XmlConnectionsDeserializer.cs`
|
||||
- CSV: Update `CsvConnectionsSerializerMremotengFormat.cs`
|
||||
5. **Add localization** in `Language/Language.resx`
|
||||
6. **Write tests** in `mRemoteNGTests/Connection/`
|
||||
7. **Update documentation** in `mRemoteNGDocumentation/` if user-facing
|
||||
|
||||
#### UI Controls
|
||||
- Prefer existing mRemoteNG patterns for UI controls
|
||||
- Connection panels use `InterfaceControl` base class
|
||||
- Custom painting: Override `OnPaint` or handle `Paint` event
|
||||
- Follow Windows Forms best practices
|
||||
|
||||
## Testing Guidelines
|
||||
|
||||
### Test Structure
|
||||
- Use NUnit's `[Test]` attribute for test methods
|
||||
- Use `[SetUp]` and `[TearDown]` for test initialization/cleanup
|
||||
- Use `[TestCase]` for parameterized tests
|
||||
- Use NSubstitute for mocking: `Substitute.For<IInterface>()`
|
||||
|
||||
### Test Naming
|
||||
- Use descriptive names: `MethodName_Scenario_ExpectedBehavior`
|
||||
- Example: `ConnectionInfo_SetPassword_EncryptsValue`
|
||||
|
||||
### Test Organization
|
||||
Mirror the main project structure in `mRemoteNGTests/`:
|
||||
- `Connection/` for connection tests
|
||||
- `Security/` for security tests
|
||||
- `Config/` for configuration/serialization tests
|
||||
|
||||
## Important Notes and Pitfalls
|
||||
|
||||
### Backward Compatibility
|
||||
- **Critical**: Never break loading of old connection files
|
||||
- Always provide default values for new properties
|
||||
- Test that files without new attributes still load correctly
|
||||
|
||||
### Security
|
||||
- Sensitive data (passwords, credentials) must be encrypted
|
||||
- Use existing security providers in `Security/` namespace
|
||||
- Never log or expose credentials in plain text
|
||||
|
||||
### Performance
|
||||
- Connection tree can contain thousands of nodes
|
||||
- Optimize for large connection files
|
||||
- Avoid unnecessary UI refreshes
|
||||
- Use async/await for I/O operations
|
||||
|
||||
### Platform-Specific Code
|
||||
- The project targets both x64 and ARM64
|
||||
- Avoid platform-specific code unless absolutely necessary
|
||||
- Test on both platforms when possible
|
||||
|
||||
### External Dependencies
|
||||
- PuTTY for SSH/Telnet (bundled)
|
||||
- Terminal Service Client for RDP
|
||||
- Various protocol-specific libraries (see CREDITS.md)
|
||||
|
||||
## Documentation
|
||||
|
||||
### User Documentation
|
||||
- Located in `mRemoteNGDocumentation/`
|
||||
- Written in reStructuredText (.rst)
|
||||
- Follows ReadTheDocs format
|
||||
- Include screenshots and examples for new features
|
||||
|
||||
### Code Documentation
|
||||
- Use XML documentation comments for public APIs
|
||||
- Document complex algorithms and business logic
|
||||
- Keep implementation notes in `IMPLEMENTATION_NOTES.md` for significant features
|
||||
|
||||
## Common Tasks
|
||||
|
||||
### Adding a Protocol
|
||||
1. Create protocol implementation in `Connection/Protocol/`
|
||||
2. Add protocol enum value to protocol types
|
||||
3. Implement connection logic
|
||||
4. Add UI controls if needed
|
||||
5. Update documentation
|
||||
|
||||
### Adding a Theme
|
||||
1. Add theme files to `Themes/` directory
|
||||
2. Update theme manager
|
||||
3. Add documentation in `mRemoteNGDocumentation/themes/`
|
||||
|
||||
### Updating Dependencies
|
||||
- Dependencies are centrally managed in `Directory.Packages.props`
|
||||
- Use `dotnet restore` after updating
|
||||
- Test thoroughly after dependency updates
|
||||
|
||||
## Version and Release
|
||||
|
||||
- Current development version: 1.78.2-dev
|
||||
- Version is set in `mRemoteNG.csproj`
|
||||
- Builds are automated via GitHub Actions (`.github/workflows/`)
|
||||
- Changelog maintained in `CHANGELOG.md`
|
||||
|
||||
## Getting Help
|
||||
|
||||
- Project website: https://mremoteng.org
|
||||
- Documentation: https://mremoteng.readthedocs.io
|
||||
- GitHub Issues: Report bugs and feature requests
|
||||
- Community: Reddit r/mRemoteNG, Matrix chat
|
||||
|
||||
## Summary
|
||||
|
||||
When contributing to mRemoteNG:
|
||||
1. Follow the existing code style (EditorConfig)
|
||||
2. Maintain backward compatibility with old connection files
|
||||
3. Localize all user-facing strings
|
||||
4. Write tests for new functionality
|
||||
5. Update documentation for user-facing changes
|
||||
6. Test on both x64 and ARM64 if possible
|
||||
7. Keep security and performance in mind
|
||||
284
.github/workflows/Build_mR-NB.yml
vendored
Normal file
284
.github/workflows/Build_mR-NB.yml
vendored
Normal file
@@ -0,0 +1,284 @@
|
||||
name: Build_and_Release_mR-NB_MultiDeploy
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- v1.78.2-dev
|
||||
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
release_flag:
|
||||
description: 'Run NB release'
|
||||
required: false
|
||||
default: 'true'
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
NB-Build-and-Release:
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
# Framework-Dependent builds (requires .NET runtime on user machine)
|
||||
- runner: windows-latest
|
||||
platform: x64
|
||||
arch: x64
|
||||
deployment: framework-dependent
|
||||
deploy_suffix: FD
|
||||
- runner: windows-11-arm
|
||||
platform: ARM64
|
||||
arch: arm64
|
||||
deployment: framework-dependent
|
||||
deploy_suffix: FD
|
||||
# Self-Contained builds (includes .NET runtime)
|
||||
- runner: windows-latest
|
||||
platform: x64
|
||||
arch: x64
|
||||
deployment: self-contained
|
||||
deploy_suffix: SC
|
||||
- runner: windows-11-arm
|
||||
platform: ARM64
|
||||
arch: arm64
|
||||
deployment: self-contained
|
||||
deploy_suffix: SC
|
||||
runs-on: ${{ matrix.runner }}
|
||||
# Only run if:
|
||||
# - manual dispatch, OR
|
||||
# - push event AND commit message contains "NB release"
|
||||
if: >
|
||||
github.event_name == 'workflow_dispatch' ||
|
||||
(github.event_name == 'push' && contains(github.event.head_commit.message, 'NB release'))
|
||||
|
||||
steps:
|
||||
- name: (01) Checkout Repository
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: (02) Install .NET 10 SDK
|
||||
uses: actions/setup-dotnet@v5
|
||||
with:
|
||||
dotnet-version: '10.0.x'
|
||||
|
||||
- name: (03) Setup MSBuild
|
||||
uses: microsoft/setup-msbuild@v2
|
||||
with:
|
||||
vs-version: '18.0'
|
||||
|
||||
- name: (04) Install and run dotnet-t4 to transform T4 templates
|
||||
shell: pwsh
|
||||
run: |
|
||||
dotnet tool install --global dotnet-t4
|
||||
# Refresh PATH to include global tools
|
||||
$env:PATH += ";$env:USERPROFILE\.dotnet\tools"
|
||||
$ttFile = "$env:GITHUB_WORKSPACE\mRemoteNG\Properties\AssemblyInfo.tt"
|
||||
# VS Enterprise 2022 assemblies
|
||||
$vsPath = "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\Common7\IDE\PublicAssemblies"
|
||||
Write-Host "Transforming T4 template"
|
||||
t4 $ttFile -P platformType=${{ matrix.platform }} -r:"$vsPath\EnvDTE.dll" -r:"$vsPath\Microsoft.VisualStudio.Interop.dll"
|
||||
env:
|
||||
PLATFORM: '${{ matrix.platform }}'
|
||||
|
||||
- name: (05) Cache NuGet Packages
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: ~/.nuget/packages
|
||||
key: ${{ runner.os }}-${{ matrix.arch }}-${{ matrix.deployment }}-nuget-${{ hashFiles('**/*.csproj') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-${{ matrix.arch }}-${{ matrix.deployment }}-nuget-
|
||||
${{ runner.os }}-${{ matrix.arch }}-nuget-
|
||||
|
||||
- name: (06) Restore NuGet Packages
|
||||
shell: pwsh
|
||||
run: dotnet restore
|
||||
|
||||
- name: (07) Build Framework-Dependent Release
|
||||
if: matrix.deployment == 'framework-dependent'
|
||||
shell: pwsh
|
||||
run: |
|
||||
msbuild "$Env:GITHUB_WORKSPACE\mRemoteNG.sln" -p:Configuration="Release" -p:Platform=${{ matrix.platform }} /verbosity:minimal
|
||||
|
||||
- name: (08) Build Self-Contained Release
|
||||
if: matrix.deployment == 'self-contained'
|
||||
shell: pwsh
|
||||
run: |
|
||||
msbuild "$Env:GITHUB_WORKSPACE\mRemoteNG.sln" /t:Publish /p:Configuration="Release Self-Contained" -p:Platform=${{ matrix.platform }} /verbosity:minimal /p:SelfContained=true /p:PublishDir="bin\${{ matrix.platform }}\Release"
|
||||
|
||||
- name: (09) Release Information
|
||||
id: version
|
||||
shell: pwsh
|
||||
run: |
|
||||
$assemblyInfoPath = "${{ github.workspace }}\mRemoteNG\Properties\AssemblyInfo.cs"
|
||||
$line = Get-Content $assemblyInfoPath | Where-Object { $_ -match 'AssemblyVersion\("(.+?)"\)' }
|
||||
if ($line -match 'AssemblyVersion\("(?<ver>\d+\.\d+\.\d+)\.(?<build>\d+)"\)') {
|
||||
$version = $matches['ver']
|
||||
$build = $matches['build']
|
||||
} else {
|
||||
throw "Could not extract version and build number"
|
||||
}
|
||||
$date = Get-Date -Format "yyyyMMdd"
|
||||
$zipName = "mRemoteNG-$date-v$version-NB-$build-${{ matrix.arch }}-${{ matrix.deploy_suffix }}.zip"
|
||||
$tag = "$date-v$version-NB-($build)"
|
||||
$message = git log -1 --pretty=%B
|
||||
echo "message=$message" >> $env:GITHUB_OUTPUT
|
||||
echo "zipname=$zipName" >> $env:GITHUB_OUTPUT
|
||||
echo "version=$version" >> $env:GITHUB_OUTPUT
|
||||
echo "build=$build" >> $env:GITHUB_OUTPUT
|
||||
echo "tag=$tag" >> $env:GITHUB_OUTPUT
|
||||
echo "deployment=${{ matrix.deployment }}" >> $env:GITHUB_OUTPUT
|
||||
|
||||
- name: (10) Extract Changelog Section
|
||||
id: changelog
|
||||
shell: pwsh
|
||||
run: |
|
||||
$changelogPath = "$env:GITHUB_WORKSPACE\CHANGELOG.md"
|
||||
$lines = Get-Content $changelogPath
|
||||
|
||||
$startIndex = -1
|
||||
for ($i = 0; $i -lt $lines.Count; $i++) {
|
||||
if ($lines[$i] -match '^## \[') {
|
||||
$startIndex = $i
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if ($startIndex -eq -1) {
|
||||
throw "No version header found in CHANGELOG.md"
|
||||
}
|
||||
|
||||
$section = @()
|
||||
for ($i = $startIndex + 1; $i -lt $lines.Count; $i++) {
|
||||
if ($lines[$i] -match '^## ') {
|
||||
break
|
||||
}
|
||||
$section += $lines[$i]
|
||||
}
|
||||
|
||||
$joined = $section -join "`n"
|
||||
echo "log<<EOF" >> $env:GITHUB_OUTPUT
|
||||
echo $joined >> $env:GITHUB_OUTPUT
|
||||
echo "EOF" >> $env:GITHUB_OUTPUT
|
||||
|
||||
- name: (11) Create Zip File
|
||||
shell: pwsh
|
||||
run: |
|
||||
$sourceDir = "$Env:GITHUB_WORKSPACE\mRemoteNG\bin\${{ matrix.platform }}\Release"
|
||||
Compress-Archive -Path "$sourceDir\*" -DestinationPath ${{ steps.version.outputs.zipname }}
|
||||
echo "File: ${{ steps.version.outputs.zipname }}"
|
||||
|
||||
# Show file size
|
||||
$fileSize = (Get-Item ${{ steps.version.outputs.zipname }}).Length / 1MB
|
||||
echo "Size: $([math]::Round($fileSize, 2)) MB"
|
||||
|
||||
- name: (10) Upload artifacts for combination
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: release-${{ matrix.arch }}-${{ matrix.deploy_suffix }}
|
||||
path: ${{ steps.version.outputs.zipname }}
|
||||
retention-days: 1
|
||||
|
||||
Create-Combined-Release:
|
||||
needs: NB-Build-and-Release
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Download all artifacts
|
||||
uses: actions/download-artifact@v7
|
||||
with:
|
||||
path: artifacts
|
||||
|
||||
- name: Extract version info
|
||||
id: version
|
||||
shell: pwsh
|
||||
run: |
|
||||
$assemblyInfoPath = "${{ github.workspace }}\mRemoteNG\Properties\AssemblyInfo.cs"
|
||||
$line = Get-Content $assemblyInfoPath | Where-Object { $_ -match 'AssemblyVersion\("(.+?)"\)' }
|
||||
if ($line -match 'AssemblyVersion\("(?<ver>\d+\.\d+\.\d+)\.(?<build>\d+)"\)') {
|
||||
$version = $matches['ver']
|
||||
$build = $matches['build']
|
||||
} else {
|
||||
throw "Could not extract version and build number"
|
||||
}
|
||||
$date = Get-Date -Format "yyyyMMdd"
|
||||
$tag = "$date-v$version-NB-($build)"
|
||||
$message = git log -1 --pretty=%B
|
||||
echo "version=$version" >> $env:GITHUB_OUTPUT
|
||||
echo "build=$build" >> $env:GITHUB_OUTPUT
|
||||
echo "tag=$tag" >> $env:GITHUB_OUTPUT
|
||||
echo "message=$message" >> $env:GITHUB_OUTPUT
|
||||
|
||||
- name: Extract Changelog
|
||||
id: changelog
|
||||
shell: pwsh
|
||||
run: |
|
||||
$changelogPath = "$env:GITHUB_WORKSPACE\CHANGELOG.md"
|
||||
$lines = Get-Content $changelogPath
|
||||
|
||||
$startIndex = -1
|
||||
for ($i = 0; $i -lt $lines.Count; $i++) {
|
||||
if ($lines[$i] -match '^## \[') {
|
||||
$startIndex = $i
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if ($startIndex -eq -1) {
|
||||
throw "No version header found in CHANGELOG.md"
|
||||
}
|
||||
|
||||
$section = @()
|
||||
for ($i = $startIndex + 1; $i -lt $lines.Count; $i++) {
|
||||
if ($lines[$i] -match '^## ') {
|
||||
break
|
||||
}
|
||||
$section += $lines[$i]
|
||||
}
|
||||
|
||||
$joined = $section -join "`n"
|
||||
echo "log<<EOF" >> $env:GITHUB_OUTPUT
|
||||
echo $joined >> $env:GITHUB_OUTPUT
|
||||
echo "EOF" >> $env:GITHUB_OUTPUT
|
||||
|
||||
- name: Collect all release files
|
||||
shell: pwsh
|
||||
run: |
|
||||
Get-ChildItem -Path artifacts -Recurse -Filter "*.zip" | ForEach-Object {
|
||||
Copy-Item $_.FullName -Destination .
|
||||
echo "Found: $($_.Name)"
|
||||
}
|
||||
|
||||
- name: Create combined release
|
||||
uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b # v2
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
tag_name: ${{ steps.version.outputs.tag }}
|
||||
name: "mRemoteNG ${{ steps.version.outputs.version }} NB ${{ steps.version.outputs.build }}"
|
||||
files: '*.zip'
|
||||
body: |
|
||||
## mRemoteNG ${{ steps.version.outputs.version }} NB Build ${{ steps.version.outputs.build }}
|
||||
|
||||
### 📦 Available Downloads
|
||||
|
||||
**Framework-Dependent (FD)** - Requires .NET 10 Runtime installed:
|
||||
- Smaller download size (~15-25 MB)
|
||||
- Requires .NET 10 Desktop Runtime on user machine
|
||||
- Files: `*-FD.zip`
|
||||
|
||||
**Self-Contained (SC)** - Portable, no installation required:
|
||||
- Larger download size (~80-150 MB)
|
||||
- Includes .NET 10 runtime - no installation needed
|
||||
- Files: `*-SC.zip`
|
||||
|
||||
Both versions are available for **x64** and **ARM64** architectures.
|
||||
|
||||
---
|
||||
|
||||
### 📝 Changes in this Release
|
||||
${{ steps.changelog.outputs.log }}
|
||||
|
||||
### 💬 Last Commit Message
|
||||
${{ steps.version.outputs.message }}
|
||||
|
||||
draft: false
|
||||
prerelease: true
|
||||
126
.github/workflows/add_PR_2_chlog.yml
vendored
Normal file
126
.github/workflows/add_PR_2_chlog.yml
vendored
Normal file
@@ -0,0 +1,126 @@
|
||||
name: Update Changelog After Renovate PR Merge
|
||||
|
||||
on:
|
||||
# 1) Auto on pushes to your repo’s default branch (merge commits included)
|
||||
push:
|
||||
branches:
|
||||
- v1.78.2-dev
|
||||
|
||||
# 2) Manual trigger
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
dryRun:
|
||||
description: 'Run without committing changes'
|
||||
required: false
|
||||
default: 'true'
|
||||
|
||||
jobs:
|
||||
update-changelog:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
# Only proceed if…
|
||||
# - manual dispatch
|
||||
# - OR a push to default branch
|
||||
if: |
|
||||
github.event_name == 'workflow_dispatch' ||
|
||||
github.event_name == 'push'
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Check for Renovate dependency update
|
||||
id: check-renovate
|
||||
shell: bash
|
||||
run: |
|
||||
if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then
|
||||
echo "isRenovate=true" >> $GITHUB_OUTPUT
|
||||
exit 0
|
||||
fi
|
||||
msg="$(git log -1 --pretty=%B)"
|
||||
if echo "$msg" | grep -q 'chore(deps): update dependency'; then
|
||||
echo "isRenovate=true" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "isRenovate=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Abort if not a Renovate PR
|
||||
if: steps.check-renovate.outputs.isRenovate == 'false'
|
||||
run: |
|
||||
echo "ℹ️ Last commit is not a Renovate dependency update—skipping."
|
||||
|
||||
- name: Parse Renovate PR info
|
||||
if: steps.check-renovate.outputs.isRenovate == 'true'
|
||||
id: extract
|
||||
shell: pwsh
|
||||
run: |
|
||||
# 1) Determine dryRun
|
||||
$dryRun = '${{ github.event.inputs.dryRun }}'
|
||||
if (-not $dryRun) { $dryRun = 'false' }
|
||||
Write-Host "🔍 dryRun = $dryRun"
|
||||
|
||||
# 2) Read full commit message
|
||||
$fullMsg = git log -1 --pretty=%B
|
||||
Write-Host "📝 Commit message:"; Write-Host $fullMsg
|
||||
|
||||
# 3) Extract PR number
|
||||
if ($fullMsg -match 'Merge pull request #(\d+)') {
|
||||
$prNumber = $matches[1]
|
||||
} else {
|
||||
throw "❌ Could not locate PR number in merge commit"
|
||||
}
|
||||
|
||||
# 4) Extract dependency name & version
|
||||
if ($fullMsg -match 'chore\(deps\): update dependency ([\w\.\-]+) to ([\d\.]+)') {
|
||||
$depName = $matches[1]
|
||||
$depVersion = $matches[2]
|
||||
} else {
|
||||
throw "❌ Could not parse dependency name/version"
|
||||
}
|
||||
|
||||
# 5) Export outputs
|
||||
echo "pr=$prNumber" >> $env:GITHUB_OUTPUT
|
||||
echo "depName=$depName" >> $env:GITHUB_OUTPUT
|
||||
echo "depVersion=$depVersion" >> $env:GITHUB_OUTPUT
|
||||
echo "dryRun=$dryRun" >> $env:GITHUB_OUTPUT
|
||||
|
||||
- name: Update CHANGELOG.md
|
||||
if: steps.check-renovate.outputs.isRenovate == 'true'
|
||||
shell: pwsh
|
||||
run: |
|
||||
$path = "$env:GITHUB_WORKSPACE/CHANGELOG.md"
|
||||
if (-not (Test-Path $path)) { throw "❌ CHANGELOG.md not found" }
|
||||
$lines = Get-Content $path
|
||||
$pr = '${{ steps.extract.outputs.pr }}'
|
||||
$dep = '${{ steps.extract.outputs.depName }}'
|
||||
$ver = '${{ steps.extract.outputs.depVersion }}'
|
||||
$entry = "- #$pr: update dependency $dep to $ver"
|
||||
|
||||
# Find latest version header: ## [x.y.z]
|
||||
$vIndex = $lines.FindIndex({ $_ -match '^## \[\d+\.\d+\.\d+\]' })
|
||||
if ($vIndex -lt 0) { throw "❌ No version header found" }
|
||||
|
||||
# Locate or create "### Dependency update"
|
||||
$depIndex = -1
|
||||
for ($i = $vIndex + 1; $i -lt $lines.Count; $i++) {
|
||||
if ($lines[$i] -match '^### Dependency update') { $depIndex = $i; break }
|
||||
if ($lines[$i] -match '^## ') { break }
|
||||
}
|
||||
if ($depIndex -eq -1) {
|
||||
$lines.Insert($vIndex + 1, '### Dependency update')
|
||||
$depIndex = $vIndex + 1
|
||||
}
|
||||
|
||||
# Insert the changelog entry
|
||||
$lines.Insert($depIndex + 1, $entry)
|
||||
Set-Content -Path $path -Value $lines
|
||||
Write-Host "✅ Inserted in CHANGELOG.md:"; Write-Host " $entry"
|
||||
|
||||
- name: Commit & push
|
||||
if: steps.check-renovate.outputs.isRenovate == 'true' && steps.extract.outputs.dryRun != 'true'
|
||||
run: |
|
||||
git config user.name "github-actions"
|
||||
git config user.email "github-actions@github.com"
|
||||
git add CHANGELOG.md
|
||||
git commit -m "docs: update changelog for #${{ steps.extract.outputs.pr }}"
|
||||
git push
|
||||
130
.github/workflows/build-x86_64.yml
vendored
130
.github/workflows/build-x86_64.yml
vendored
@@ -1,130 +0,0 @@
|
||||
name: Build x86_64
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- v1.77.3-dev
|
||||
|
||||
jobs:
|
||||
Build-Debug-MSI:
|
||||
runs-on: windows-2022
|
||||
steps:
|
||||
- name: 01. Copy repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: 02. Setup MSBuild.exe
|
||||
uses: microsoft/setup-msbuild@v1.3.1 # Add MSBuild to the PATH: https://github.com/microsoft/setup-msbuild
|
||||
with:
|
||||
vs-version: '17.12.4'
|
||||
|
||||
- name: 03. Restore nuget packages for solution
|
||||
shell: pwsh
|
||||
run: |
|
||||
dotnet restore
|
||||
|
||||
- name: 04. Compile mRemoteNG
|
||||
shell: pwsh
|
||||
run: |
|
||||
msbuild "$Env:GITHUB_WORKSPACE\mRemoteNG.sln" -p:Configuration="Debug Installer" -p:Platform=x64 /verbosity:normal
|
||||
|
||||
- name: 05. Publish MSI as Artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: debug-msi-x86_64
|
||||
path: ${{ github.workspace }}\mRemoteNGInstaller\Installer\bin\x64\Debug\en-US\
|
||||
if-no-files-found: error
|
||||
|
||||
Build-Debug-Portable:
|
||||
runs-on: windows-2022
|
||||
steps:
|
||||
- name: 01. Copy repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: 02. Setup MSBuild.exe
|
||||
uses: microsoft/setup-msbuild@v1.3.1 # Add MSBuild to the PATH: https://github.com/microsoft/setup-msbuild
|
||||
with:
|
||||
vs-version: '17.8.3'
|
||||
|
||||
- name: 03. Restore nuget packages for solution
|
||||
shell: pwsh
|
||||
run: |
|
||||
dotnet restore
|
||||
|
||||
- name: 04. Compile mRemoteNG
|
||||
shell: pwsh
|
||||
run: |
|
||||
msbuild "$Env:GITHUB_WORKSPACE\mRemoteNG.sln" -p:Configuration="Debug Portable" -p:Platform=x64 /verbosity:normal
|
||||
|
||||
- name: 05. Publish Portable Binary as Artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: debug-portable-x86_64
|
||||
path: ${{ github.workspace }}\mRemoteNG\bin\x64\Debug Portable\
|
||||
if-no-files-found: error
|
||||
|
||||
Build-Release:
|
||||
runs-on: windows-2022
|
||||
steps:
|
||||
- name: 01. Copy repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: 02. Setup MSBuild.exe
|
||||
uses: microsoft/setup-msbuild@v1.3.1 # Add MSBuild to the PATH: https://github.com/microsoft/setup-msbuild
|
||||
with:
|
||||
vs-version: '17.12.4'
|
||||
|
||||
- name: 03. Restore nuget packages for solution
|
||||
shell: pwsh
|
||||
run: |
|
||||
dotnet restore
|
||||
|
||||
- name: 04. Compile mRemoteNG
|
||||
shell: pwsh
|
||||
run: |
|
||||
msbuild "$Env:GITHUB_WORKSPACE\mRemoteNG.sln" -p:Configuration="Release Installer and Portable" -p:Platform=x64 /verbosity:normal
|
||||
|
||||
- name: 05. Publish MSI Binary as Artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: release-msi-x86_64
|
||||
path: ${{ github.workspace }}\mRemoteNGInstaller\Installer\bin\x64\Release\en-US\
|
||||
if-no-files-found: error
|
||||
|
||||
- name: 06. Publish Portable Binary as Artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: release-portable-x86_64
|
||||
path: ${{ github.workspace }}\mRemoteNG\bin\x64\Release
|
||||
if-no-files-found: error
|
||||
|
||||
Create-Release:
|
||||
needs: [Build-Debug-MSI, Build-Debug-Portable, Build-Release]
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: 01. Copy repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: 02. Download Artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
|
||||
- name: 03. Create compressed archives # Needs to be done because "actions/download-artifact@v4" is extracting the zipped Artifacts
|
||||
shell: bash
|
||||
run: |
|
||||
zip -r debug-msi-x86_64.zip debug-msi-x86_64/
|
||||
zip -r debug-portable-x86_64.zip debug-portable-x86_64/
|
||||
zip -r release-msi-x86_64.zip release-msi-x86_64/
|
||||
zip -r release-portable-x86_64.zip release-portable-x86_64/
|
||||
|
||||
- name: 04. Create Release
|
||||
shell: bash
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
gh release create "v1.77.3-dev-${GITHUB_RUN_NUMBER}" \
|
||||
--title "v1.77.3-dev-${GITHUB_RUN_NUMBER}" \
|
||||
--prerelease \
|
||||
--generate-notes \
|
||||
$GITHUB_WORKSPACE/debug-msi-x86_64.zip \
|
||||
$GITHUB_WORKSPACE/debug-portable-x86_64.zip \
|
||||
$GITHUB_WORKSPACE/release-msi-x86_64.zip \
|
||||
$GITHUB_WORKSPACE/release-portable-x86_64.zip
|
||||
49
.github/workflows/post_2_Reddit.yml
vendored
Normal file
49
.github/workflows/post_2_Reddit.yml
vendored
Normal file
@@ -0,0 +1,49 @@
|
||||
name: Post to Reddit via PowerShell
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
post:
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- name: Authenticate and post to Reddit
|
||||
shell: pwsh
|
||||
env:
|
||||
CLIENT_ID: ${{ secrets.REDDIT_CLIENT_ID }}
|
||||
CLIENT_SECRET: ${{ secrets.REDDIT_CLIENT_SECRET }}
|
||||
USERNAME: ${{ secrets.REDDIT_USERNAME }}
|
||||
PASSWORD: ${{ secrets.REDDIT_PASSWORD }}
|
||||
SUBREDDIT: ${{ secrets.REDDIT_SUBREDDIT }}
|
||||
run: |
|
||||
# Step 1: Get access token
|
||||
$authHeaders = @{
|
||||
Authorization = "Basic " + [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes("$env:CLIENT_ID:$env:CLIENT_SECRET"))
|
||||
"User-Agent" = "github-to-reddit-pwsh/0.1"
|
||||
}
|
||||
|
||||
$authBody = @{
|
||||
grant_type = "password"
|
||||
username = $env:USERNAME
|
||||
password = $env:PASSWORD
|
||||
}
|
||||
|
||||
$authResponse = Invoke-RestMethod -Uri "https://www.reddit.com/api/v1/access_token" -Method Post -Headers $authHeaders -Body $authBody
|
||||
$token = $authResponse.access_token
|
||||
|
||||
# Step 2: Post to subreddit
|
||||
$postHeaders = @{
|
||||
Authorization = "bearer $token"
|
||||
"User-Agent" = "github-to-reddit-pwsh/0.1"
|
||||
}
|
||||
|
||||
$postBody = @{
|
||||
sr = $env:SUBREDDIT
|
||||
title = "Hello from GitHub Actions (PowerShell)"
|
||||
kind = "self"
|
||||
text = "This post was made using PowerShell in GitHub Actions."
|
||||
}
|
||||
|
||||
$postResponse = Invoke-RestMethod -Uri "https://oauth.reddit.com/api/submit" -Method Post -Headers $postHeaders -Body $postBody
|
||||
Write-Host "Posted: $($postResponse.json.url)"
|
||||
|
||||
39
CHANGELOG.md
39
CHANGELOG.md
@@ -2,6 +2,45 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
## [1.78.2]
|
||||
### Fixed
|
||||
- #2939: fixed SQL injection vulnerabilities via parameterized queries
|
||||
- #2940: fixed for Possible command injection via Process.Start
|
||||
- #2932: fixed HTTP/HTTPS protocol to support multiple concurrent connections
|
||||
- #2855: fixed missing Username field for HTTP and HTTPS protocols
|
||||
- #2852: fixed XML External Entity (XXE) vulnerability in XML deserialization
|
||||
- #2851: fixed path traversal vulnerability in file operations
|
||||
- #2850: fixed password dialog appearing behind splash screen on startup
|
||||
- #2842: fixed element placement in options window
|
||||
- #2734: fixed native build for Windows-x64
|
||||
- #2715: fixed Disable WinForms analyzers and suppress WFO1000 build errors for ObjectListView
|
||||
- #2712: fixed VNCEvent_Disconnected send the ProtocolBase based object reference
|
||||
- #2668: fixed ssh quickconnect exception
|
||||
- #2611: fixed correct registry path
|
||||
- #2496: fixed use pwfile instead of cleartext password for putty connections
|
||||
|
||||
### Added
|
||||
- #2931: added vault openbao connector
|
||||
- #2900: added "Report a Bug" menu item to Help menu and update bug report URL to GitHub issues
|
||||
- #2865: added configurable connection tab colors to distinguish between different environments
|
||||
- #2864: added color property to connections and folders with inheritance support
|
||||
- #2863: added ARD (Apple Remote Desktop) protocol support for macOS connections
|
||||
- #2728: added support for building mRemoteNG on Windows ARM64
|
||||
- #2723: added read keyboardhook, gatewayaccesstoken and gatewaycredentialssource from RDP File
|
||||
- #2690: added தமிழ் (ta) Translation update
|
||||
- #2643: added registry Settings: enhancements and new settings implementation
|
||||
- #2591: add Clickstudios Passwordstate API connector
|
||||
- #2212: added 1Password integration
|
||||
|
||||
### Updated
|
||||
- #2883: Improve 1Password logging
|
||||
- #2854: Refactor settings dialog to be opened in dockable panel (for consistency)
|
||||
- #2597: Remember the opened connection file on relaunch
|
||||
- #2502: Updated Polish translation
|
||||
|
||||
### Dependency update
|
||||
- #3bd2fe8: puttyng updated to x64 version (and signed)
|
||||
|
||||
## [1.77.3.1784]
|
||||
### Fixed
|
||||
- #2362: Fix use of sql database
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
# Contributors
|
||||
|
||||
## Current mRemoteNG dev team
|
||||
[Dimitrij Gorodeckij](https://github.com/Kvarkas)
|
||||
|
||||
## Past Contributors
|
||||
[David Sparer](http://github.com/sparerd)
|
||||
[Sean Kaim](http://github.com/kmscode)
|
||||
[Faryan Rezagholi](http://github.com/farosch)
|
||||
@@ -28,8 +30,6 @@ Tony Lambert
|
||||
[MitchellBot](http://github.com/MitchellBot)
|
||||
[Filippo Ferrazini](http://github.com/Filippo125)
|
||||
|
||||
## Past Contributors
|
||||
|
||||
Felix Deimel - mRemote original developer
|
||||
Riley McArdle - mRemoteNG original developer
|
||||
|
||||
|
||||
167
DEPLOYMENT_OPTIONS.md
Normal file
167
DEPLOYMENT_OPTIONS.md
Normal file
@@ -0,0 +1,167 @@
|
||||
# mRemoteNG Deployment Options
|
||||
|
||||
This document explains the two deployment options for mRemoteNG and how to build each version.
|
||||
|
||||
## Deployment Types
|
||||
|
||||
### 1. Framework-Dependent (FD)
|
||||
**File suffix: `-FD.zip`**
|
||||
|
||||
- **Size**: ~15-25 MB
|
||||
- **Requirements**: User must have .NET 10 Desktop Runtime installed
|
||||
- **Startup**: Application checks for .NET runtime and prompts user to download if missing
|
||||
- **Use case**: Standard release for users comfortable installing prerequisites
|
||||
|
||||
### 2. Self-Contained (SC)
|
||||
**File suffix: `-SC.zip`**
|
||||
|
||||
- **Size**: ~80-150 MB
|
||||
- **Requirements**: None - includes .NET 10 runtime
|
||||
- **Startup**: No runtime checks performed (runtime is bundled)
|
||||
- **Use case**: Portable version for users who want zero installation/configuration
|
||||
|
||||
## Building Locally
|
||||
|
||||
### Framework-Dependent Build
|
||||
|
||||
```powershell
|
||||
# x64
|
||||
msbuild mRemoteNG.sln -p:Configuration=Release -p:Platform=x64
|
||||
|
||||
# ARM64
|
||||
msbuild mRemoteNG.sln -p:Configuration=Release -p:Platform=ARM64
|
||||
```
|
||||
|
||||
### Self-Contained Build
|
||||
|
||||
```powershell
|
||||
# x64
|
||||
dotnet publish mRemoteNG\mRemoteNG.csproj `
|
||||
--configuration Release `
|
||||
--runtime win-x64 `
|
||||
--self-contained true `
|
||||
-p:Platform=x64 `
|
||||
-p:PublishSingleFile=false `
|
||||
-p:PublishReadyToRun=true `
|
||||
-p:DefineConstants="SELF_CONTAINED"
|
||||
|
||||
# ARM64
|
||||
dotnet publish mRemoteNG\mRemoteNG.csproj `
|
||||
--configuration Release `
|
||||
--runtime win-arm64 `
|
||||
--self-contained true `
|
||||
-p:Platform=ARM64 `
|
||||
-p:PublishSingleFile=false `
|
||||
-p:PublishReadyToRun=true `
|
||||
-p:DefineConstants="SELF_CONTAINED"
|
||||
```
|
||||
|
||||
## GitHub Actions Workflow
|
||||
|
||||
The new workflow file `Build_and_Release_mR-NB-MultiDeploy.yml` automatically builds all four variants:
|
||||
|
||||
1. **x64 Framework-Dependent** - `mRemoteNG-YYYYMMDD-vX.X.X-NB-XXXX-x64-FD.zip`
|
||||
2. **x64 Self-Contained** - `mRemoteNG-YYYYMMDD-vX.X.X-NB-XXXX-x64-SC.zip`
|
||||
3. **ARM64 Framework-Dependent** - `mRemoteNG-YYYYMMDD-vX.X.X-NB-XXXX-arm64-FD.zip`
|
||||
4. **ARM64 Self-Contained** - `mRemoteNG-YYYYMMDD-vX.X.X-NB-XXXX-arm64-SC.zip`
|
||||
|
||||
### Workflow Triggers
|
||||
|
||||
The workflow runs when:
|
||||
- You push to `v1.78.2-dev` branch with commit message containing "NB release"
|
||||
- You manually trigger via workflow_dispatch
|
||||
|
||||
### Release Output
|
||||
|
||||
All four zip files are uploaded to a single GitHub Release with clear descriptions:
|
||||
- Framework-Dependent versions are marked as requiring .NET 10 Runtime
|
||||
- Self-Contained versions are marked as portable/no installation needed
|
||||
|
||||
## Code Changes
|
||||
|
||||
### ProgramRoot.cs
|
||||
|
||||
The `MainAsync` method now uses conditional compilation:
|
||||
|
||||
```csharp
|
||||
#if !SELF_CONTAINED
|
||||
// Runtime check code only included in Framework-Dependent builds
|
||||
// Checks for .NET Runtime and Visual C++ Redistributable
|
||||
#endif
|
||||
```
|
||||
|
||||
When building with `-p:DefineConstants="SELF_CONTAINED"`, the runtime checks are completely excluded from the compiled binary.
|
||||
|
||||
## Recommendations
|
||||
|
||||
### For Users
|
||||
|
||||
**Choose Framework-Dependent (FD) if:**
|
||||
- You don't mind installing .NET 10 Desktop Runtime once
|
||||
- You want smaller download size
|
||||
- You're using multiple .NET applications (runtime is shared)
|
||||
|
||||
**Choose Self-Contained (SC) if:**
|
||||
- You want zero installation/setup
|
||||
- You need portable deployment (USB drive, restricted environments)
|
||||
- You don't want to deal with prerequisites
|
||||
|
||||
### For Distribution
|
||||
|
||||
Consider offering both options:
|
||||
- Make Framework-Dependent the **default/recommended** option (smaller, faster updates)
|
||||
- Offer Self-Contained as **portable alternative** for special use cases
|
||||
|
||||
## Technical Details
|
||||
|
||||
### Compilation Symbols
|
||||
|
||||
- Framework-Dependent builds: No special symbols
|
||||
- Self-Contained builds: `SELF_CONTAINED` symbol defined
|
||||
|
||||
### Runtime Identifiers
|
||||
|
||||
- x64: `win-x64`
|
||||
- ARM64: `win-arm64`
|
||||
|
||||
### Publish Options
|
||||
|
||||
Self-contained builds use these optimizations:
|
||||
- `PublishReadyToRun=true` - AOT compilation for faster startup
|
||||
- `IncludeNativeLibrariesForSelfExtract=true` - Bundle native dependencies
|
||||
- `PublishSingleFile=false` - Keep files separate for better compatibility with mRemoteNG's plugin system
|
||||
|
||||
## File Size Comparison
|
||||
|
||||
Typical build sizes:
|
||||
|
||||
| Version | Framework-Dependent | Self-Contained |
|
||||
|---------|---------------------|----------------|
|
||||
| x64 | ~18 MB | ~95 MB |
|
||||
| ARM64 | ~18 MB | ~95 MB |
|
||||
|
||||
*Note: Self-contained includes entire .NET 10 runtime (~80 MB overhead)*
|
||||
|
||||
## Testing
|
||||
|
||||
### Framework-Dependent Build
|
||||
1. Uninstall .NET 10 Runtime (if installed)
|
||||
2. Run mRemoteNG.exe
|
||||
3. Should prompt to download .NET 10 Runtime
|
||||
4. Install runtime and verify app launches
|
||||
|
||||
### Self-Contained Build
|
||||
1. Uninstall .NET 10 Runtime (if installed)
|
||||
2. Run mRemoteNG.exe
|
||||
3. Should launch immediately without runtime check
|
||||
4. Verify full functionality
|
||||
|
||||
## Migration from Old Workflow
|
||||
|
||||
The original workflow `Build_and_Release_mR-NB.yml` is preserved. To migrate:
|
||||
|
||||
1. Rename or remove old workflow: `Build_and_Release_mR-NB.yml`
|
||||
2. Rename new workflow: `Build_and_Release_mR-NB-MultiDeploy.yml` → `Build_and_Release_mR-NB.yml`
|
||||
3. Commit and push with "NB release" in message
|
||||
|
||||
|
||||
105
Directory.Packages.props
Normal file
105
Directory.Packages.props
Normal file
@@ -0,0 +1,105 @@
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
|
||||
<CentralPackageTransitivePinningEnabled>true</CentralPackageTransitivePinningEnabled>
|
||||
<NoWarn>$(NoWarn);NU1507;NU1701</NoWarn>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageVersion Include="AWSSDK.Core" Version="4.0.3.14" />
|
||||
<PackageVersion Include="AWSSDK.EC2" Version="4.0.76" />
|
||||
<PackageVersion Include="BouncyCastle.Cryptography" Version="2.6.2" />
|
||||
<PackageVersion Include="Castle.Core" Version="5.2.1" />
|
||||
<PackageVersion Include="ConsoleControl" Version="1.3.0" />
|
||||
<PackageVersion Include="ConsoleControlAPI" Version="1.3.0" />
|
||||
<PackageVersion Include="coverlet.collector" Version="8.0.0" />
|
||||
<PackageVersion Include="Cucumber.Messages" Version="32.0.1" />
|
||||
<PackageVersion Include="DockPanelSuite" Version="3.1.1" />
|
||||
<PackageVersion Include="DockPanelSuite.ThemeVS2015" Version="3.1.1" />
|
||||
<PackageVersion Include="envdte" Version="17.14.40260" />
|
||||
<PackageVersion Include="Gherkin" Version="38.0.0" />
|
||||
<PackageVersion Include="Google.Protobuf" Version="3.33.5" />
|
||||
<PackageVersion Include="LiteDB" Version="5.0.21" />
|
||||
<PackageVersion Include="log4net" Version="3.2.0" />
|
||||
<PackageVersion Include="Microsoft.Data.SqlClient" Version="6.1.4" />
|
||||
<PackageVersion Include="Microsoft.Data.SqlClient.SNI" Version="6.0.2" />
|
||||
<PackageVersion Include="Microsoft.Data.SqlClient.SNI.runtime" Version="6.0.2" />
|
||||
<PackageVersion Include="Microsoft.Extensions.DependencyModel" Version="10.0.3" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Configuration.UserSecrets" Version="10.0.3" />
|
||||
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="18.0.1" />
|
||||
<PackageVersion Include="Microsoft.NETCore.Platforms" Version="7.0.4" />
|
||||
<PackageVersion Include="Microsoft.NETCore.Targets" Version="5.0.0" />
|
||||
<PackageVersion Include="Microsoft.VisualStudio.TextTemplating.VSHost" Version="17.14.40265" />
|
||||
<PackageVersion Include="Microsoft.Web.WebView2" Version="1.0.3800.47" />
|
||||
<PackageVersion Include="Microsoft.Xaml.Behaviors.Wpf" Version="1.1.135" />
|
||||
<PackageVersion Include="Microsoft-WindowsAPICodePack-Shell" Version="1.1.5" />
|
||||
<PackageVersion Include="MySql.Data" Version="9.6.0" />
|
||||
<PackageVersion Include="NETStandard.Library" Version="2.0.3" />
|
||||
<PackageVersion Include="Newtonsoft.Json" Version="13.0.4" />
|
||||
<PackageVersion Include="Newtonsoft.Json.Schema" Version="4.0.1" />
|
||||
<PackageVersion Include="NSubstitute" Version="5.3.0" />
|
||||
<PackageVersion Include="NUnit" Version="4.5.0" />
|
||||
<PackageVersion Include="NUnit.Console" Version="3.22.0" />
|
||||
<PackageVersion Include="NUnit.ConsoleRunner" Version="3.22.0" />
|
||||
<PackageVersion Include="NUnit.Extension.TeamCityEventListener" Version="1.0.10" />
|
||||
<PackageVersion Include="NUnit.Runners" Version="3.12.0" />
|
||||
<PackageVersion Include="NUnit3TestAdapter" Version="6.1.0" />
|
||||
<PackageVersion Include="OpenCover" Version="4.7.1221" />
|
||||
<PackageVersion Include="Renci.SshNet.Async" Version="1.4.0" />
|
||||
<PackageVersion Include="ReportGenerator" Version="5.5.1" />
|
||||
<PackageVersion Include="runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl" Version="4.3.3" />
|
||||
<PackageVersion Include="runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl" Version="4.3.3" />
|
||||
<PackageVersion Include="runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl" Version="4.3.3" />
|
||||
<PackageVersion Include="runtime.native.System" Version="4.3.1" />
|
||||
<PackageVersion Include="runtime.native.System.IO.Compression" Version="4.3.2" />
|
||||
<PackageVersion Include="runtime.native.System.Net.Http" Version="4.3.1" />
|
||||
<PackageVersion Include="runtime.native.System.Security.Cryptography.OpenSsl" Version="4.3.3" />
|
||||
<PackageVersion Include="runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl" Version="4.3.3" />
|
||||
<PackageVersion Include="runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl" Version="4.3.3" />
|
||||
<PackageVersion Include="runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl" Version="4.3.3" />
|
||||
<PackageVersion Include="SSH.NET" Version="2025.1.0" />
|
||||
<PackageVersion Include="System.Buffers" Version="4.6.1" />
|
||||
<PackageVersion Include="System.Configuration.ConfigurationManager" Version="10.0.3" />
|
||||
<PackageVersion Include="System.Collections.Immutable" Version="10.0.3" />
|
||||
<PackageVersion Include="System.Console" Version="4.3.1" />
|
||||
<PackageVersion Include="System.Data.Common" Version="4.3.0" />
|
||||
<PackageVersion Include="System.Diagnostics.DiagnosticSource" Version="10.0.3" />
|
||||
<PackageVersion Include="System.Drawing.Common" Version="10.0.3" />
|
||||
<PackageVersion Include="System.Diagnostics.EventLog" Version="10.0.3" />
|
||||
<PackageVersion Include="System.DirectoryServices" Version="10.0.3" />
|
||||
<PackageVersion Include="System.Dynamic.Runtime" Version="4.3.0" />
|
||||
<PackageVersion Include="System.IO.Pipelines" Version="10.0.3" />
|
||||
<PackageVersion Include="System.Formats.Asn1" Version="10.0.3" />
|
||||
<PackageVersion Include="System.Management" Version="10.0.3" />
|
||||
<PackageVersion Include="System.Memory" Version="4.6.3" />
|
||||
<PackageVersion Include="System.Net.Http" Version="4.3.4" />
|
||||
<PackageVersion Include="System.Net.Primitives" Version="4.3.1" />
|
||||
<PackageVersion Include="System.Net.Sockets" Version="4.3.0" />
|
||||
<PackageVersion Include="System.Reflection.Emit" Version="4.7.0" />
|
||||
<PackageVersion Include="System.Reflection.Emit.ILGeneration" Version="4.7.0" />
|
||||
<PackageVersion Include="System.Reflection.Emit.Lightweight" Version="4.7.0" />
|
||||
<PackageVersion Include="System.Reflection.Metadata" Version="10.0.3" />
|
||||
<PackageVersion Include="System.Reflection.TypeExtensions" Version="4.7.0" />
|
||||
<PackageVersion Include="System.Resources.ResourceManager" Version="4.3.0" />
|
||||
<PackageVersion Include="System.Runtime" Version="4.3.1" />
|
||||
<PackageVersion Include="System.Runtime.CompilerServices.Unsafe" Version="6.1.2" />
|
||||
<PackageVersion Include="System.Runtime.Extensions" Version="4.3.1" />
|
||||
<PackageVersion Include="System.Security.AccessControl" Version="6.0.1" />
|
||||
<PackageVersion Include="System.Security.Cryptography.Algorithms" Version="4.3.1" />
|
||||
<PackageVersion Include="System.Security.Cryptography.Cng" Version="5.0.0" />
|
||||
<PackageVersion Include="System.Security.Cryptography.OpenSsl" Version="5.0.0" />
|
||||
<PackageVersion Include="System.Security.Cryptography.ProtectedData" Version="10.0.3" />
|
||||
<PackageVersion Include="System.Security.Cryptography.X509Certificates" Version="4.3.2" />
|
||||
<PackageVersion Include="System.Security.Permissions" Version="10.0.3" />
|
||||
<PackageVersion Include="System.Security.Principal.Windows" Version="5.0.0" />
|
||||
<PackageVersion Include="System.Text.Encoding.CodePages" Version="10.0.3" />
|
||||
<PackageVersion Include="System.Text.Json" Version="10.0.3" />
|
||||
<PackageVersion Include="System.Text.RegularExpressions" Version="4.3.1" />
|
||||
<PackageVersion Include="System.Threading.Tasks.Extensions" Version="4.6.3" />
|
||||
<PackageVersion Include="System.ValueTuple" Version="4.6.1" />
|
||||
<PackageVersion Include="System.Windows.Extensions" Version="10.0.3" />
|
||||
<PackageVersion Include="System.Xml.ReaderWriter" Version="4.3.1" />
|
||||
<PackageVersion Include="VaultSharp" Version="1.17.5.1" />
|
||||
<PackageVersion Include="VncSharpCore" Version="1.2.1" />
|
||||
<PackageVersion Include="ZstdSharp.Port" Version="0.8.7" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -257,15 +257,9 @@ public class PasswordstateInterface
|
||||
|
||||
return ""+textWriter.ToString();
|
||||
}
|
||||
private class PasswordFinder : IPasswordFinder
|
||||
private class PasswordFinder(string password) : IPasswordFinder
|
||||
{
|
||||
private string password;
|
||||
|
||||
public PasswordFinder(string password)
|
||||
{
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
private string password = password;
|
||||
|
||||
public char[] GetPassword()
|
||||
{
|
||||
|
||||
@@ -58,10 +58,7 @@ namespace SecretServerAuthentication.DSS
|
||||
/// <param name="refresh_token">The refresh token. Required when refreshing a token.</param>
|
||||
/// <returns>Successful retrieval of an access token</returns>
|
||||
/// <exception cref="ApiException">A server side error occurred.</exception>
|
||||
public System.Threading.Tasks.Task<TokenResponse> AuthorizeAsync(Grant_type grant_type, string username, string password, string refresh_token, string OTP)
|
||||
{
|
||||
return AuthorizeAsync(grant_type, username, password, refresh_token, System.Threading.CancellationToken.None, OTP);
|
||||
}
|
||||
public System.Threading.Tasks.Task<TokenResponse> AuthorizeAsync(Grant_type grant_type, string username, string password, string refresh_token, string OTP) => AuthorizeAsync(grant_type, username, password, refresh_token, System.Threading.CancellationToken.None, OTP);
|
||||
|
||||
/// <param name="cancellationToken">A cancellation token that can be used by other objects or threads to receive notice of cancellation.</param>
|
||||
/// <summary>Retrieve or Refresh Access Token</summary>
|
||||
@@ -355,10 +352,7 @@ namespace SecretServerAuthentication.DSS
|
||||
Headers = headers;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("HTTP Response: \n\n{0}\n\n{1}", Response, base.ToString());
|
||||
}
|
||||
public override string ToString() => string.Format("HTTP Response: \n\n{0}\n\n{1}", Response, base.ToString());
|
||||
}
|
||||
|
||||
[System.CodeDom.Compiler.GeneratedCode("NSwag", "13.14.8.0 (NJsonSchema v10.5.2.0 (Newtonsoft.Json v11.0.0.0))")]
|
||||
|
||||
@@ -223,15 +223,9 @@ public class SecretServerInterface
|
||||
|
||||
return ""+textWriter.ToString();
|
||||
}
|
||||
private class PasswordFinder : IPasswordFinder
|
||||
private class PasswordFinder(string password) : IPasswordFinder
|
||||
{
|
||||
private string password;
|
||||
|
||||
public PasswordFinder(string password)
|
||||
{
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
private string password = password;
|
||||
|
||||
public char[] GetPassword()
|
||||
{
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,31 +1,33 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0-windows7.0</TargetFramework>
|
||||
<TargetFramework>net10.0-windows10.0.26100.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<OutputType>Library</OutputType>
|
||||
<UseWindowsForms>True</UseWindowsForms>
|
||||
<Platforms>x64</Platforms>
|
||||
<Configurations>Debug;Release;Debug Portable;Release Portable;Deploy to github</Configurations>
|
||||
<SupportedOSPlatformVersion>7.0</SupportedOSPlatformVersion>
|
||||
<Platforms>x64;arm64</Platforms>
|
||||
<Configurations>Debug;Release;Debug Portable;Release Self-Contained;Deploy to github</Configurations>
|
||||
<SupportedOSPlatformVersion>10.0.22621.0</SupportedOSPlatformVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release Portable|x64'">
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release Self-Contained|x64'">
|
||||
<Optimize>True</Optimize>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release Self-Contained|arm64'">
|
||||
<Optimize>True</Optimize>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="AWSSDK.Core" Version="3.7.402.1" />
|
||||
<PackageReference Include="AWSSDK.EC2" Version="3.7.430.2" />
|
||||
<PackageReference Include="BouncyCastle.Cryptography" Version="2.5.1" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
<PackageReference Include="AWSSDK.Core" />
|
||||
<PackageReference Include="AWSSDK.EC2" />
|
||||
<PackageReference Include="BouncyCastle.Cryptography" />
|
||||
<PackageReference Include="Newtonsoft.Json" />
|
||||
<PackageReference Include="VaultSharp" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Update="AWS\AWSConnectionForm.cs" />
|
||||
<Compile Update="CPS\CPSConnectionForm.cs" />
|
||||
<Compile Update="DSS\SSConnectionForm.cs" />
|
||||
<Compile Update="VO\VaultOpenbaoConnectionForm.cs">
|
||||
<SubType>Form</SubType>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
131
ExternalConnectors/OP/OnePasswordCli.cs
Normal file
131
ExternalConnectors/OP/OnePasswordCli.cs
Normal file
@@ -0,0 +1,131 @@
|
||||
using System.Diagnostics;
|
||||
using System.Net;
|
||||
using System.Text.Json;
|
||||
using System.Web;
|
||||
|
||||
namespace ExternalConnectors.OP;
|
||||
|
||||
public class OnePasswordCliException(string message, string arguments) : Exception(message)
|
||||
{
|
||||
public string Arguments { get; set; } = arguments;
|
||||
}
|
||||
|
||||
public class OnePasswordCli
|
||||
{
|
||||
private const string OnePasswordCliExecutable = "op.exe";
|
||||
|
||||
// Username / password purpose metadata is used on Login category item fields
|
||||
private const string UserNamePurpose = "USERNAME";
|
||||
private const string PasswordPurpose = "PASSWORD";
|
||||
|
||||
// Server category items (and perhaps others) do have a built-in username/password field but don't have the `purpose` set
|
||||
// and because it's a built-in field this can't be set afterwards.
|
||||
// We use the label for as fallback because that can be user-modified to fit this convention in all cases.
|
||||
private const string UserNameLabel = "username";
|
||||
private const string PasswordLabel = "password";
|
||||
|
||||
|
||||
private const string StringType = "STRING";
|
||||
private const string SshKeyType = "SSHKEY";
|
||||
private const string DomainLabel = "domain";
|
||||
|
||||
private record VaultUrl(string Label, string Href);
|
||||
|
||||
private record VaultField(string Id, string Label, string Type, string Purpose, string Value);
|
||||
|
||||
private record VaultItem(VaultUrl[]? Urls, VaultField[]? Fields);
|
||||
|
||||
private static readonly JsonSerializerOptions JsonSerializerOptions = new()
|
||||
{
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
|
||||
};
|
||||
|
||||
public static void ReadPassword(string input, out string username, out string password, out string domain, out string privateKey)
|
||||
{
|
||||
var inputUrl = new Uri(input);
|
||||
var vault = WebUtility.UrlDecode(inputUrl.Host);
|
||||
var queryParams = HttpUtility.ParseQueryString(inputUrl.Query);
|
||||
var account = queryParams["account"];
|
||||
var item = WebUtility.UrlDecode(inputUrl.AbsolutePath.TrimStart('/'));
|
||||
ItemGet(item, vault, account, out username, out password, out domain, out privateKey);
|
||||
}
|
||||
|
||||
private static void ItemGet(string item, string? vault, string? account, out string username, out string password, out string domain, out string privateKey)
|
||||
{
|
||||
var args = new List<string> { "item", "get", item };
|
||||
|
||||
if (!string.IsNullOrEmpty(account))
|
||||
{
|
||||
args.Add("--account");
|
||||
args.Add(account);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(vault))
|
||||
{
|
||||
args.Add("--vault");
|
||||
args.Add(vault);
|
||||
}
|
||||
|
||||
args.Add("--format");
|
||||
args.Add("json");
|
||||
|
||||
string commandLine = OnePasswordCliExecutable + " " + string.Join(' ', args);
|
||||
|
||||
var exitCode = RunCommand(OnePasswordCliExecutable, args, out var output, out var error);
|
||||
if (exitCode != 0)
|
||||
{
|
||||
username = string.Empty;
|
||||
password = string.Empty;
|
||||
privateKey = string.Empty;
|
||||
domain = string.Empty;
|
||||
throw new OnePasswordCliException($"Error running op item get: {error}",
|
||||
commandLine);
|
||||
}
|
||||
|
||||
var items = JsonSerializer.Deserialize<VaultItem>(output, JsonSerializerOptions) ??
|
||||
throw new OnePasswordCliException("1Password returned null",
|
||||
commandLine);
|
||||
username = FindField(items, UserNamePurpose, UserNameLabel);
|
||||
password = FindField(items, PasswordPurpose, PasswordLabel);
|
||||
privateKey = items.Fields?.FirstOrDefault(x => x.Type == SshKeyType)?.Value ?? string.Empty;
|
||||
domain = items.Fields?.FirstOrDefault(x => x.Type == StringType && x.Label == DomainLabel)?.Value ?? string.Empty;
|
||||
if(string.IsNullOrEmpty(password) && string.IsNullOrEmpty(privateKey))
|
||||
{
|
||||
throw new OnePasswordCliException("No secret found in 1Password. At least fields with labels username/password or a SshKey are expected.", commandLine);
|
||||
}
|
||||
}
|
||||
|
||||
private static string FindField(VaultItem items, string purpose, string fallbackLabel)
|
||||
{
|
||||
return items.Fields?.FirstOrDefault(x => x.Purpose == purpose)?.Value ??
|
||||
items.Fields?.FirstOrDefault(x => x.Type == StringType && string.Equals(x.Id, fallbackLabel, StringComparison.InvariantCultureIgnoreCase))?.Value ??
|
||||
items.Fields?.FirstOrDefault(x => x.Type == StringType && string.Equals(x.Label, fallbackLabel, StringComparison.InvariantCultureIgnoreCase))?.Value ??
|
||||
string.Empty;
|
||||
}
|
||||
|
||||
private static int RunCommand(string command, IReadOnlyCollection<string> arguments, out string output,
|
||||
out string error)
|
||||
{
|
||||
var processStartInfo = new ProcessStartInfo
|
||||
{
|
||||
FileName = command,
|
||||
RedirectStandardOutput = true,
|
||||
RedirectStandardError = true,
|
||||
UseShellExecute = false,
|
||||
CreateNoWindow = true
|
||||
};
|
||||
|
||||
foreach (var argument in arguments)
|
||||
{
|
||||
processStartInfo.ArgumentList.Add(argument);
|
||||
}
|
||||
|
||||
using var process = new Process();
|
||||
process.StartInfo = processStartInfo;
|
||||
process.Start();
|
||||
output = process.StandardOutput.ReadToEnd();
|
||||
error = process.StandardError.ReadToEnd();
|
||||
process.WaitForExit();
|
||||
return process.ExitCode;
|
||||
}
|
||||
}
|
||||
105
ExternalConnectors/VO/VaultOpenbao.cs
Normal file
105
ExternalConnectors/VO/VaultOpenbao.cs
Normal file
@@ -0,0 +1,105 @@
|
||||
using Microsoft.Win32;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using VaultSharp;
|
||||
using VaultSharp.V1.AuthMethods;
|
||||
using VaultSharp.V1.AuthMethods.Token;
|
||||
|
||||
namespace ExternalConnectors.VO {
|
||||
public class VaultOpenbaoException(string message, string? arguments = null) : Exception(message) {
|
||||
public string Arguments { get; set; } = arguments ?? string.Empty;
|
||||
}
|
||||
|
||||
public static class VaultOpenbao {
|
||||
private static readonly RegistryKey baseKey = Registry.CurrentUser.CreateSubKey(@"SOFTWARE\mRemoteVaultOpenbao");
|
||||
private static string token = "";
|
||||
private static VaultClient GetClient() {
|
||||
string url = (string)baseKey.GetValue("URL", "");
|
||||
using VaultOpenbaoConnectionForm voForm = new();
|
||||
voForm.tbUrl.Text = url;
|
||||
voForm.tbToken.Text = token;
|
||||
_ = voForm.ShowDialog();
|
||||
if (voForm.DialogResult != DialogResult.OK)
|
||||
throw new VaultOpenbaoException($"No credential provided");
|
||||
url = voForm.tbUrl.Text;
|
||||
if (!string.IsNullOrEmpty(voForm.tbToken.Text)) // override token if provided
|
||||
token = voForm.tbToken.Text;
|
||||
IAuthMethodInfo authMethod = new TokenAuthMethodInfo(token);
|
||||
var vaultClientSettings = new VaultClientSettings(url, authMethod);
|
||||
VaultClient client = new(vaultClientSettings);
|
||||
var sysInfo = client.V1.System.GetInitStatusAsync().Result;
|
||||
if (!sysInfo) {
|
||||
MessageBox.Show("Test connection failed", "Vault Openbao", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
throw new VaultOpenbaoException("Url not working");
|
||||
}
|
||||
baseKey.SetValue("URL", url);
|
||||
return client;
|
||||
}
|
||||
private static void TestMountType(VaultClient vaultClient, string mount, int VaultOpenbaoSecretEngine) {
|
||||
switch (vaultClient.V1.System.GetSecretBackendAsync(mount).Result.Data.Type.Type) {
|
||||
case "kv" when VaultOpenbaoSecretEngine != 0:
|
||||
throw new VaultOpenbaoException($"Backend of type kv does not match expected type {VaultOpenbaoSecretEngine}");
|
||||
case "ldap" when VaultOpenbaoSecretEngine != 1 && VaultOpenbaoSecretEngine != 2:
|
||||
throw new VaultOpenbaoException($"Backend of type ldap does not match expected type {VaultOpenbaoSecretEngine}");
|
||||
case "ssh" when VaultOpenbaoSecretEngine != 3:
|
||||
throw new VaultOpenbaoException($"Backend of type ssh does not match expected type {VaultOpenbaoSecretEngine}");
|
||||
}
|
||||
}
|
||||
public static void ReadOtpSSH(string mount, string role, string? username, string address, out string password) {
|
||||
VaultClient vaultClient = GetClient();
|
||||
TestMountType(vaultClient, mount, 3);
|
||||
if (!IPAddress.TryParse(address, out _)) {
|
||||
try {
|
||||
var addrs = Dns.GetHostAddressesAsync(address).Result;
|
||||
if (addrs == null || addrs.Length == 0) {
|
||||
throw new VaultOpenbaoException($"Could not resolve address '{address}'");
|
||||
}
|
||||
// Prefer IPv4, otherwise take first available
|
||||
var selected = addrs.FirstOrDefault(a => a.AddressFamily == AddressFamily.InterNetwork) ?? addrs[0];
|
||||
address = selected.ToString();
|
||||
} catch (Exception ex) {
|
||||
throw new VaultOpenbaoException($"Failed to resolve address '{address}'", ex.Message);
|
||||
}
|
||||
}
|
||||
var otp = vaultClient.V1.Secrets.SSH.GetCredentialsAsync(role, address, username, mount).Result;
|
||||
password = otp.Data.Key;
|
||||
|
||||
}
|
||||
public static void ReadPasswordSSH(int secretEngine, string mount, string role, string username, out string password) {
|
||||
VaultClient vaultClient = GetClient();
|
||||
TestMountType(vaultClient, mount, secretEngine);
|
||||
switch (secretEngine) {
|
||||
case 0:
|
||||
var kv = vaultClient.V1.Secrets.KeyValue.V2.ReadSecretAsync(role, mountPoint: mount).Result;
|
||||
password = kv.Data.Data[username].ToString() ?? string.Empty;
|
||||
return;
|
||||
default:
|
||||
throw new VaultOpenbaoException($"Backend of type {secretEngine} is not supported");
|
||||
}
|
||||
}
|
||||
public static void ReadPasswordRDP(int secretEngine, string mount, string role, ref string username, out string password) {
|
||||
VaultClient vaultClient = GetClient();
|
||||
TestMountType(vaultClient, mount, secretEngine);
|
||||
switch (secretEngine) {
|
||||
case 0:
|
||||
var kv = vaultClient.V1.Secrets.KeyValue.V2.ReadSecretAsync(role, mountPoint: mount).Result;
|
||||
password = kv.Data.Data[username].ToString() ?? string.Empty;
|
||||
return;
|
||||
case 1:
|
||||
var ldapd = vaultClient.V1.Secrets.OpenLDAP.GetDynamicCredentialsAsync(role, mount).Result;
|
||||
username = ldapd.Data.Username;
|
||||
password = ldapd.Data.Password;
|
||||
return;
|
||||
case 2:
|
||||
var ldaps = vaultClient.V1.Secrets.OpenLDAP.GetStaticCredentialsAsync(role, mount).Result;
|
||||
username = ldaps.Data.Username;
|
||||
password = ldaps.Data.Password;
|
||||
return;
|
||||
default:
|
||||
throw new VaultOpenbaoException($"Backend of type {secretEngine} is not supported");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
185
ExternalConnectors/VO/VaultOpenbaoConnectionForm.Designer.cs
generated
Normal file
185
ExternalConnectors/VO/VaultOpenbaoConnectionForm.Designer.cs
generated
Normal file
@@ -0,0 +1,185 @@
|
||||
namespace ExternalConnectors.VO
|
||||
{
|
||||
partial class VaultOpenbaoConnectionForm
|
||||
{
|
||||
/// <summary>
|
||||
/// Required designer variable.
|
||||
/// </summary>
|
||||
private System.ComponentModel.IContainer components = null;
|
||||
|
||||
/// <summary>
|
||||
/// Clean up any resources being used.
|
||||
/// </summary>
|
||||
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing && (components != null))
|
||||
{
|
||||
components.Dispose();
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
#region Windows Form Designer generated code
|
||||
|
||||
/// <summary>
|
||||
/// Required method for Designer support - do not modify
|
||||
/// the contents of this method with the code editor.
|
||||
/// </summary>
|
||||
private void InitializeComponent() {
|
||||
tbUrl = new TextBox();
|
||||
tbToken = new TextBox();
|
||||
btnOK = new Button();
|
||||
btnCancel = new Button();
|
||||
tableLayoutPanel1 = new TableLayoutPanel();
|
||||
label1 = new Label();
|
||||
label2 = new Label();
|
||||
tableLayoutPanel2 = new TableLayoutPanel();
|
||||
tableLayoutPanel1.SuspendLayout();
|
||||
tableLayoutPanel2.SuspendLayout();
|
||||
SuspendLayout();
|
||||
//
|
||||
// tbUrl
|
||||
//
|
||||
tbUrl.Dock = DockStyle.Fill;
|
||||
tbUrl.Location = new Point(174, 5);
|
||||
tbUrl.Margin = new Padding(5);
|
||||
tbUrl.Name = "tbUrl";
|
||||
tbUrl.Size = new Size(559, 27);
|
||||
tbUrl.TabIndex = 0;
|
||||
//
|
||||
// tbToken
|
||||
//
|
||||
tbToken.Dock = DockStyle.Fill;
|
||||
tbToken.Location = new Point(174, 57);
|
||||
tbToken.Margin = new Padding(5);
|
||||
tbToken.Name = "tbToken";
|
||||
tbToken.Size = new Size(559, 27);
|
||||
tbToken.TabIndex = 2;
|
||||
tbToken.UseSystemPasswordChar = true;
|
||||
tbToken.Focus();
|
||||
//
|
||||
// btnOK
|
||||
//
|
||||
btnOK.Anchor = AnchorStyles.Right;
|
||||
btnOK.DialogResult = DialogResult.OK;
|
||||
btnOK.Location = new Point(250, 16);
|
||||
btnOK.Margin = new Padding(5);
|
||||
btnOK.Name = "btnOK";
|
||||
btnOK.Size = new Size(101, 35);
|
||||
btnOK.TabIndex = 10;
|
||||
btnOK.Text = "OK";
|
||||
btnOK.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// btnCancel
|
||||
//
|
||||
btnCancel.Anchor = AnchorStyles.Left;
|
||||
btnCancel.DialogResult = DialogResult.Cancel;
|
||||
btnCancel.Location = new Point(387, 16);
|
||||
btnCancel.Margin = new Padding(5);
|
||||
btnCancel.Name = "btnCancel";
|
||||
btnCancel.Size = new Size(101, 35);
|
||||
btnCancel.TabIndex = 11;
|
||||
btnCancel.Text = "Cancel";
|
||||
btnCancel.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// tableLayoutPanel1
|
||||
//
|
||||
tableLayoutPanel1.ColumnCount = 2;
|
||||
tableLayoutPanel1.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 22.92994F));
|
||||
tableLayoutPanel1.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 77.07006F));
|
||||
tableLayoutPanel1.Controls.Add(label1, 0, 0);
|
||||
tableLayoutPanel1.Controls.Add(label2, 0, 1);
|
||||
tableLayoutPanel1.Controls.Add(tbUrl, 1, 0);
|
||||
tableLayoutPanel1.Controls.Add(tbToken, 1, 1);
|
||||
tableLayoutPanel1.Dock = DockStyle.Top;
|
||||
tableLayoutPanel1.Location = new Point(0, 0);
|
||||
tableLayoutPanel1.Margin = new Padding(5);
|
||||
tableLayoutPanel1.Name = "tableLayoutPanel1";
|
||||
tableLayoutPanel1.RowCount = 3;
|
||||
tableLayoutPanel1.RowStyles.Add(new RowStyle(SizeType.Percent, 50F));
|
||||
tableLayoutPanel1.RowStyles.Add(new RowStyle(SizeType.Percent, 50F));
|
||||
tableLayoutPanel1.RowStyles.Add(new RowStyle(SizeType.Absolute, 31F));
|
||||
tableLayoutPanel1.RowStyles.Add(new RowStyle(SizeType.Absolute, 31F));
|
||||
tableLayoutPanel1.RowStyles.Add(new RowStyle(SizeType.Absolute, 31F));
|
||||
tableLayoutPanel1.Size = new Size(738, 136);
|
||||
tableLayoutPanel1.TabIndex = 12;
|
||||
//
|
||||
// label1
|
||||
//
|
||||
label1.AutoSize = true;
|
||||
label1.Dock = DockStyle.Fill;
|
||||
label1.Location = new Point(5, 0);
|
||||
label1.Margin = new Padding(5, 0, 5, 0);
|
||||
label1.Name = "label1";
|
||||
label1.Size = new Size(159, 52);
|
||||
label1.TabIndex = 2;
|
||||
label1.Text = "Server URL";
|
||||
label1.TextAlign = ContentAlignment.MiddleLeft;
|
||||
//
|
||||
// label2
|
||||
//
|
||||
label2.AutoSize = true;
|
||||
label2.Dock = DockStyle.Fill;
|
||||
label2.Location = new Point(5, 52);
|
||||
label2.Margin = new Padding(5, 0, 5, 0);
|
||||
label2.Name = "label2";
|
||||
label2.Size = new Size(159, 52);
|
||||
label2.TabIndex = 4;
|
||||
label2.Text = "Access Token";
|
||||
label2.TextAlign = ContentAlignment.MiddleLeft;
|
||||
//
|
||||
// tableLayoutPanel2
|
||||
//
|
||||
tableLayoutPanel2.ColumnCount = 5;
|
||||
tableLayoutPanel2.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 106F));
|
||||
tableLayoutPanel2.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 50F));
|
||||
tableLayoutPanel2.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 26F));
|
||||
tableLayoutPanel2.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 50F));
|
||||
tableLayoutPanel2.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 106F));
|
||||
tableLayoutPanel2.Controls.Add(btnOK, 1, 0);
|
||||
tableLayoutPanel2.Controls.Add(btnCancel, 3, 0);
|
||||
tableLayoutPanel2.Dock = DockStyle.Bottom;
|
||||
tableLayoutPanel2.Location = new Point(0, 149);
|
||||
tableLayoutPanel2.Margin = new Padding(5);
|
||||
tableLayoutPanel2.Name = "tableLayoutPanel2";
|
||||
tableLayoutPanel2.RowCount = 1;
|
||||
tableLayoutPanel2.RowStyles.Add(new RowStyle(SizeType.Percent, 100F));
|
||||
tableLayoutPanel2.Size = new Size(738, 67);
|
||||
tableLayoutPanel2.TabIndex = 13;
|
||||
//
|
||||
// VaultOpenbaoConnectionForm
|
||||
//
|
||||
AcceptButton = btnOK;
|
||||
AutoScaleDimensions = new SizeF(8F, 20F);
|
||||
AutoScaleMode = AutoScaleMode.Font;
|
||||
ClientSize = new Size(738, 216);
|
||||
Controls.Add(tableLayoutPanel2);
|
||||
Controls.Add(tableLayoutPanel1);
|
||||
FormBorderStyle = FormBorderStyle.FixedDialog;
|
||||
Margin = new Padding(5);
|
||||
MaximizeBox = false;
|
||||
MinimizeBox = false;
|
||||
Name = "VaultOpenbaoConnectionForm";
|
||||
SizeGripStyle = SizeGripStyle.Hide;
|
||||
Text = "Vault/Openbao API Login Data";
|
||||
Activated += VaultOpenbaoConnectionForm_Activated;
|
||||
tableLayoutPanel1.ResumeLayout(false);
|
||||
tableLayoutPanel1.PerformLayout();
|
||||
tableLayoutPanel2.ResumeLayout(false);
|
||||
ResumeLayout(false);
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public System.Windows.Forms.TextBox tbUrl;
|
||||
public System.Windows.Forms.TextBox tbToken;
|
||||
private System.Windows.Forms.Button btnOK;
|
||||
private System.Windows.Forms.Button btnCancel;
|
||||
private System.Windows.Forms.TableLayoutPanel tableLayoutPanel1;
|
||||
private System.Windows.Forms.Label label2;
|
||||
private System.Windows.Forms.Label label1;
|
||||
private System.Windows.Forms.TableLayoutPanel tableLayoutPanel2;
|
||||
}
|
||||
}
|
||||
13
ExternalConnectors/VO/VaultOpenbaoConnectionForm.cs
Normal file
13
ExternalConnectors/VO/VaultOpenbaoConnectionForm.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
namespace ExternalConnectors.VO
|
||||
{
|
||||
public partial class VaultOpenbaoConnectionForm : Form {
|
||||
public VaultOpenbaoConnectionForm() {
|
||||
InitializeComponent();
|
||||
|
||||
}
|
||||
|
||||
private void VaultOpenbaoConnectionForm_Activated(object sender, EventArgs e) {
|
||||
tbUrl.Focus();
|
||||
}
|
||||
}
|
||||
}
|
||||
120
ExternalConnectors/VO/VaultOpenbaoConnectionForm.resx
Normal file
120
ExternalConnectors/VO/VaultOpenbaoConnectionForm.resx
Normal file
@@ -0,0 +1,120 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
</root>
|
||||
158
IMPLEMENTATION_NOTES.md
Normal file
158
IMPLEMENTATION_NOTES.md
Normal file
@@ -0,0 +1,158 @@
|
||||
# Connection Frame Color Feature - Implementation Summary
|
||||
|
||||
## Overview
|
||||
This implementation adds a "Connection Frame Color" feature to mRemoteNG that allows users to visually distinguish between different connection environments (e.g., production, test, development) by displaying a colored border around connection panels.
|
||||
|
||||
## Feature Request Context
|
||||
The feature was requested in issue to prevent accidental operations on production systems. The user cited DBeaver database management tool as an example, which uses a red frame to indicate production database connections.
|
||||
|
||||
## Implementation Details
|
||||
|
||||
### 1. Data Model (Connection/ConnectionFrameColor.cs)
|
||||
Created an enum with the following values:
|
||||
- **None** (default): No colored border
|
||||
- **Red**: Intended for production environments
|
||||
- **Yellow**: Intended for staging/UAT environments
|
||||
- **Green**: Intended for test environments
|
||||
- **Blue**: Intended for development environments
|
||||
- **Purple**: Intended for custom/other environments
|
||||
|
||||
### 2. Property Addition (Connection/AbstractConnectionRecord.cs)
|
||||
- Added `ConnectionFrameColor` property to the base connection record class
|
||||
- Property is categorized under "Display" section in PropertyGrid
|
||||
- Uses EnumTypeConverter for proper display in PropertyGrid
|
||||
- Includes localized descriptions
|
||||
|
||||
### 3. Inheritance Support (Connection/ConnectionInfoInheritance.cs)
|
||||
- Added `ConnectionFrameColor` inheritance property
|
||||
- Allows folders to set a frame color that child connections can inherit
|
||||
- Follows the same pattern as other inheritable properties
|
||||
|
||||
### 4. Serialization
|
||||
|
||||
#### XML Serialization (Config/Serializers/ConnectionSerializers/Xml/)
|
||||
- **XmlConnectionNodeSerializer28.cs**: Serializes ConnectionFrameColor as an XML attribute
|
||||
- **XmlConnectionsDeserializer.cs**: Deserializes ConnectionFrameColor from XML
|
||||
- Includes inheritance attribute handling (InheritConnectionFrameColor)
|
||||
- Backward compatible: old files without this attribute will default to None
|
||||
|
||||
#### CSV Serialization (Config/Serializers/ConnectionSerializers/Csv/)
|
||||
- **CsvConnectionsSerializerMremotengFormat.cs**: Added ConnectionFrameColor to CSV export
|
||||
- Includes both value and inheritance columns
|
||||
- Maintains CSV column order consistency
|
||||
|
||||
### 5. Visual Rendering (Connection/InterfaceControl.cs)
|
||||
- Added custom Paint event handler to InterfaceControl
|
||||
- Draws a 4-pixel colored border around the connection panel when ConnectionFrameColor is set
|
||||
- Uses specific colors:
|
||||
- Red: RGB(220, 53, 69) - Bootstrap danger red
|
||||
- Yellow: RGB(255, 193, 7) - Warning yellow
|
||||
- Green: RGB(40, 167, 69) - Success green
|
||||
- Blue: RGB(0, 123, 255) - Primary blue
|
||||
- Purple: RGB(111, 66, 193) - Purple
|
||||
- Border is drawn inside the control bounds to avoid clipping
|
||||
|
||||
### 6. Localization (Language/Language.resx)
|
||||
Added language resources for:
|
||||
- ConnectionFrameColor: "Connection Frame Color"
|
||||
- PropertyDescriptionConnectionFrameColor: Description shown in PropertyGrid
|
||||
- FrameColorNone: "None"
|
||||
- FrameColorRed: "Red (Production)"
|
||||
- FrameColorYellow: "Yellow (Staging/UAT)"
|
||||
- FrameColorGreen: "Green (Test)"
|
||||
- FrameColorBlue: "Blue (Development)"
|
||||
- FrameColorPurple: "Purple (Custom)"
|
||||
|
||||
### 7. Documentation (mRemoteNGDocumentation/howtos/connection_frame_color.rst)
|
||||
Created comprehensive documentation including:
|
||||
- Overview and purpose
|
||||
- Step-by-step usage instructions
|
||||
- Visual examples
|
||||
- Inheritance explanation
|
||||
- Best practices for environment organization
|
||||
- Troubleshooting guide
|
||||
|
||||
## Technical Design Decisions
|
||||
|
||||
### Why 4-pixel border?
|
||||
- Wide enough to be immediately noticeable
|
||||
- Not so wide as to obscure content
|
||||
- Consistent with modern UI design patterns
|
||||
|
||||
### Why these specific colors?
|
||||
- Colors chosen based on common conventions:
|
||||
- Red = danger/production (universal warning color)
|
||||
- Yellow = caution/staging (standard warning color)
|
||||
- Green = safe/test (universal "go" color)
|
||||
- Blue = development (calm, neutral)
|
||||
- Purple = custom (distinct but not alarming)
|
||||
- Colors use accessible, high-contrast RGB values
|
||||
|
||||
### Why enum instead of custom color picker?
|
||||
- Simpler UI (dropdown vs color picker)
|
||||
- Ensures consistency across team/organization
|
||||
- Prevents confusion from too many color choices
|
||||
- Follows principle of "convention over configuration"
|
||||
- Can be extended in future if needed
|
||||
|
||||
### Why inherit from Panel?
|
||||
- InterfaceControl is already a Panel (see InterfaceControl.Designer.cs)
|
||||
- Panel has built-in Paint event support
|
||||
- No need for additional controls or complexity
|
||||
|
||||
## Backward Compatibility
|
||||
- Old connection files (without ConnectionFrameColor attribute) automatically default to None
|
||||
- No migration needed
|
||||
- Feature is completely opt-in
|
||||
- Does not affect existing functionality
|
||||
|
||||
## Testing Recommendations
|
||||
|
||||
When testing this feature, verify:
|
||||
|
||||
1. **Property Display**: ConnectionFrameColor appears in PropertyGrid under Display section
|
||||
2. **Enum Values**: All color options appear in dropdown
|
||||
3. **Visual Rendering**: Border appears when color is selected and connection is active
|
||||
4. **Inheritance**: Setting on folder and enabling inheritance on child works correctly
|
||||
5. **Serialization**:
|
||||
- Save connection with frame color set
|
||||
- Close and reopen file
|
||||
- Verify color is preserved
|
||||
6. **CSV Export**: ConnectionFrameColor appears in exported CSV
|
||||
7. **Backward Compatibility**: Open old connection files without errors
|
||||
|
||||
## Future Enhancements (Out of Scope)
|
||||
|
||||
Potential future improvements:
|
||||
- Custom color picker support
|
||||
- Border width customization
|
||||
- Border style options (solid, dashed, etc.)
|
||||
- Tab header color indicator in addition to panel border
|
||||
- Global warning when connecting to production (confirmation dialog)
|
||||
- Audit logging for production connections
|
||||
|
||||
## Files Modified
|
||||
|
||||
1. mRemoteNG/Connection/ConnectionFrameColor.cs (NEW)
|
||||
2. mRemoteNG/Connection/AbstractConnectionRecord.cs
|
||||
3. mRemoteNG/Connection/ConnectionInfo.cs
|
||||
4. mRemoteNG/Connection/ConnectionInfoInheritance.cs
|
||||
5. mRemoteNG/Connection/InterfaceControl.cs
|
||||
6. mRemoteNG/Config/Serializers/ConnectionSerializers/Xml/XmlConnectionNodeSerializer28.cs
|
||||
7. mRemoteNG/Config/Serializers/ConnectionSerializers/Xml/XmlConnectionsDeserializer.cs
|
||||
8. mRemoteNG/Config/Serializers/ConnectionSerializers/Csv/CsvConnectionsSerializerMremotengFormat.cs
|
||||
9. mRemoteNG/Language/Language.resx
|
||||
10. mRemoteNGDocumentation/howtos/connection_frame_color.rst (NEW)
|
||||
|
||||
## Code Review Checklist
|
||||
|
||||
- [x] Property follows existing naming conventions
|
||||
- [x] Enum values are localized
|
||||
- [x] Inheritance support implemented
|
||||
- [x] XML serialization/deserialization working
|
||||
- [x] CSV serialization updated
|
||||
- [x] Visual rendering implemented
|
||||
- [x] Documentation created
|
||||
- [x] Backward compatibility maintained
|
||||
- [x] No breaking changes
|
||||
- [x] Code follows existing patterns in codebase
|
||||
@@ -46,18 +46,14 @@ namespace BrightIdeasSoftware
|
||||
/// <summary>
|
||||
/// These items allow combo boxes to remember a value and its description.
|
||||
/// </summary>
|
||||
public class ComboBoxItem
|
||||
/// <remarks>
|
||||
///
|
||||
/// </remarks>
|
||||
/// <param name="key"></param>
|
||||
/// <param name="description"></param>
|
||||
public class ComboBoxItem(Object key, String description)
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="key"></param>
|
||||
/// <param name="description"></param>
|
||||
public ComboBoxItem(Object key, String description) {
|
||||
this.key = key;
|
||||
this.description = description;
|
||||
}
|
||||
private readonly String description;
|
||||
private readonly String description = description;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
@@ -65,7 +61,7 @@ namespace BrightIdeasSoftware
|
||||
public Object Key {
|
||||
get { return key; }
|
||||
}
|
||||
private readonly Object key;
|
||||
private readonly Object key = key;
|
||||
|
||||
/// <summary>
|
||||
/// Returns a string that represents the current object.
|
||||
|
||||
@@ -158,15 +158,12 @@ namespace BrightIdeasSoftware
|
||||
/// This class isn't intended to be used directly, but it is left as a public
|
||||
/// class just in case someone wants to subclass it.
|
||||
/// </remarks>
|
||||
public class FastObjectListDataSource : AbstractVirtualListDataSource
|
||||
/// <remarks>
|
||||
/// Create a FastObjectListDataSource
|
||||
/// </remarks>
|
||||
/// <param name="listView"></param>
|
||||
public class FastObjectListDataSource(FastObjectListView listView) : AbstractVirtualListDataSource(listView)
|
||||
{
|
||||
/// <summary>
|
||||
/// Create a FastObjectListDataSource
|
||||
/// </summary>
|
||||
/// <param name="listView"></param>
|
||||
public FastObjectListDataSource(FastObjectListView listView)
|
||||
: base(listView) {
|
||||
}
|
||||
|
||||
#region IVirtualListDataSource Members
|
||||
|
||||
|
||||
@@ -185,15 +185,11 @@ namespace BrightIdeasSoftware
|
||||
/// A model object must satisfy all filters to be included.
|
||||
/// If there are no filters, all model objects are included
|
||||
/// </summary>
|
||||
public class CompositeAllFilter : CompositeFilter {
|
||||
|
||||
/// <summary>
|
||||
/// Create a filter
|
||||
/// </summary>
|
||||
/// <param name="filters"></param>
|
||||
public CompositeAllFilter(List<IModelFilter> filters)
|
||||
: base(filters) {
|
||||
}
|
||||
/// <remarks>
|
||||
/// Create a filter
|
||||
/// </remarks>
|
||||
/// <param name="filters"></param>
|
||||
public class CompositeAllFilter(List<IModelFilter> filters) : CompositeFilter(filters) {
|
||||
|
||||
/// <summary>
|
||||
/// Decide whether or not the given model should be included by the filter
|
||||
@@ -215,15 +211,11 @@ namespace BrightIdeasSoftware
|
||||
/// A model object must only satisfy one of the filters to be included.
|
||||
/// If there are no filters, all model objects are included
|
||||
/// </summary>
|
||||
public class CompositeAnyFilter : CompositeFilter {
|
||||
|
||||
/// <summary>
|
||||
/// Create a filter from the given filters
|
||||
/// </summary>
|
||||
/// <param name="filters"></param>
|
||||
public CompositeAnyFilter(List<IModelFilter> filters)
|
||||
: base(filters) {
|
||||
}
|
||||
/// <remarks>
|
||||
/// Create a filter from the given filters
|
||||
/// </remarks>
|
||||
/// <param name="filters"></param>
|
||||
public class CompositeAnyFilter(List<IModelFilter> filters) : CompositeFilter(filters) {
|
||||
|
||||
/// <summary>
|
||||
/// Decide whether or not the given model should be included by the filter
|
||||
|
||||
@@ -50,7 +50,13 @@ namespace BrightIdeasSoftware
|
||||
/// <para>This is used by normal (non-virtual) ObjectListViews. Virtual lists use
|
||||
/// ModelObjectComparer</para>
|
||||
/// </remarks>
|
||||
public class ColumnComparer : IComparer, IComparer<OLVListItem>
|
||||
/// <remarks>
|
||||
/// Create a ColumnComparer that will order the rows in a list view according
|
||||
/// to the values in a given column
|
||||
/// </remarks>
|
||||
/// <param name="col">The column whose values will be compared</param>
|
||||
/// <param name="order">The ordering for column values</param>
|
||||
public class ColumnComparer(OLVColumn col, SortOrder order) : IComparer, IComparer<OLVListItem>
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the method that will be used to compare two strings.
|
||||
@@ -63,18 +69,6 @@ namespace BrightIdeasSoftware
|
||||
}
|
||||
private static StringCompareDelegate stringComparer;
|
||||
|
||||
/// <summary>
|
||||
/// Create a ColumnComparer that will order the rows in a list view according
|
||||
/// to the values in a given column
|
||||
/// </summary>
|
||||
/// <param name="col">The column whose values will be compared</param>
|
||||
/// <param name="order">The ordering for column values</param>
|
||||
public ColumnComparer(OLVColumn col, SortOrder order)
|
||||
{
|
||||
this.column = col;
|
||||
this.sortOrder = order;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a ColumnComparer that will order the rows in a list view according
|
||||
/// to the values in a given column, and by a secondary column if the primary
|
||||
@@ -165,8 +159,8 @@ namespace BrightIdeasSoftware
|
||||
return StringComparer(x, y);
|
||||
}
|
||||
|
||||
private OLVColumn column;
|
||||
private SortOrder sortOrder;
|
||||
private OLVColumn column = col;
|
||||
private SortOrder sortOrder = order;
|
||||
private ColumnComparer secondComparer;
|
||||
}
|
||||
|
||||
@@ -175,15 +169,12 @@ namespace BrightIdeasSoftware
|
||||
/// This comparer sort list view groups. OLVGroups have a "SortValue" property,
|
||||
/// which is used if present. Otherwise, the titles of the groups will be compared.
|
||||
/// </summary>
|
||||
public class OLVGroupComparer : IComparer<OLVGroup>
|
||||
/// <remarks>
|
||||
/// Create a group comparer
|
||||
/// </remarks>
|
||||
/// <param name="order">The ordering for column values</param>
|
||||
public class OLVGroupComparer(SortOrder order) : IComparer<OLVGroup>
|
||||
{
|
||||
/// <summary>
|
||||
/// Create a group comparer
|
||||
/// </summary>
|
||||
/// <param name="order">The ordering for column values</param>
|
||||
public OLVGroupComparer(SortOrder order) {
|
||||
this.sortOrder = order;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compare the two groups. OLVGroups have a "SortValue" property,
|
||||
@@ -207,7 +198,7 @@ namespace BrightIdeasSoftware
|
||||
return result;
|
||||
}
|
||||
|
||||
private SortOrder sortOrder;
|
||||
private SortOrder sortOrder = order;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -217,7 +208,12 @@ namespace BrightIdeasSoftware
|
||||
/// <para>This is used by virtual ObjectListViews. Non-virtual lists use
|
||||
/// ColumnComparer</para>
|
||||
/// </remarks>
|
||||
public class ModelObjectComparer : IComparer, IComparer<object>
|
||||
/// <remarks>
|
||||
/// Create a model object comparer
|
||||
/// </remarks>
|
||||
/// <param name="col"></param>
|
||||
/// <param name="order"></param>
|
||||
public class ModelObjectComparer(OLVColumn col, SortOrder order) : IComparer, IComparer<object>
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the method that will be used to compare two strings.
|
||||
@@ -230,17 +226,6 @@ namespace BrightIdeasSoftware
|
||||
}
|
||||
private static StringCompareDelegate stringComparer;
|
||||
|
||||
/// <summary>
|
||||
/// Create a model object comparer
|
||||
/// </summary>
|
||||
/// <param name="col"></param>
|
||||
/// <param name="order"></param>
|
||||
public ModelObjectComparer(OLVColumn col, SortOrder order)
|
||||
{
|
||||
this.column = col;
|
||||
this.sortOrder = order;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a model object comparer with a secondary sorting column
|
||||
/// </summary>
|
||||
@@ -318,8 +303,8 @@ namespace BrightIdeasSoftware
|
||||
return StringComparer(x, y);
|
||||
}
|
||||
|
||||
private OLVColumn column;
|
||||
private SortOrder sortOrder;
|
||||
private OLVColumn column = col;
|
||||
private SortOrder sortOrder = order;
|
||||
private ModelObjectComparer secondComparer;
|
||||
|
||||
#region IComparer<object> Members
|
||||
|
||||
@@ -878,25 +878,16 @@ namespace BrightIdeasSoftware
|
||||
/// <summary>
|
||||
/// Let the world know that a cell edit operation is beginning or ending
|
||||
/// </summary>
|
||||
public class CellEditEventArgs : EventArgs
|
||||
/// <remarks>
|
||||
/// Create an event args
|
||||
/// </remarks>
|
||||
/// <param name="column"></param>
|
||||
/// <param name="control"></param>
|
||||
/// <param name="cellBounds"></param>
|
||||
/// <param name="item"></param>
|
||||
/// <param name="subItemIndex"></param>
|
||||
public class CellEditEventArgs(OLVColumn column, Control control, Rectangle cellBounds, OLVListItem item, int subItemIndex) : EventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Create an event args
|
||||
/// </summary>
|
||||
/// <param name="column"></param>
|
||||
/// <param name="control"></param>
|
||||
/// <param name="cellBounds"></param>
|
||||
/// <param name="item"></param>
|
||||
/// <param name="subItemIndex"></param>
|
||||
public CellEditEventArgs(OLVColumn column, Control control, Rectangle cellBounds, OLVListItem item, int subItemIndex) {
|
||||
this.Control = control;
|
||||
this.column = column;
|
||||
this.cellBounds = cellBounds;
|
||||
this.listViewItem = item;
|
||||
this.rowObject = item.RowObject;
|
||||
this.subItemIndex = subItemIndex;
|
||||
this.value = column.GetValue(item.RowObject);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Change this to true to cancel the cell editing operation.
|
||||
@@ -917,7 +908,7 @@ namespace BrightIdeasSoftware
|
||||
/// entered and commit that value to the model. Changing the control during the finishing
|
||||
/// event has no effect.
|
||||
/// </summary>
|
||||
public Control Control;
|
||||
public Control Control = control;
|
||||
|
||||
/// <summary>
|
||||
/// The column of the cell that is going to be or has been edited.
|
||||
@@ -925,7 +916,7 @@ namespace BrightIdeasSoftware
|
||||
public OLVColumn Column {
|
||||
get { return this.column; }
|
||||
}
|
||||
private OLVColumn column;
|
||||
private OLVColumn column = column;
|
||||
|
||||
/// <summary>
|
||||
/// The model object of the row of the cell that is going to be or has been edited.
|
||||
@@ -933,7 +924,7 @@ namespace BrightIdeasSoftware
|
||||
public Object RowObject {
|
||||
get { return this.rowObject; }
|
||||
}
|
||||
private Object rowObject;
|
||||
private Object rowObject = item.RowObject;
|
||||
|
||||
/// <summary>
|
||||
/// The listview item of the cell that is going to be or has been edited.
|
||||
@@ -941,7 +932,7 @@ namespace BrightIdeasSoftware
|
||||
public OLVListItem ListViewItem {
|
||||
get { return this.listViewItem; }
|
||||
}
|
||||
private OLVListItem listViewItem;
|
||||
private OLVListItem listViewItem = item;
|
||||
|
||||
/// <summary>
|
||||
/// The data value of the cell as it stands in the control.
|
||||
@@ -959,7 +950,7 @@ namespace BrightIdeasSoftware
|
||||
public int SubItemIndex {
|
||||
get { return this.subItemIndex; }
|
||||
}
|
||||
private int subItemIndex;
|
||||
private int subItemIndex = subItemIndex;
|
||||
|
||||
/// <summary>
|
||||
/// The data value of the cell before the edit operation began.
|
||||
@@ -967,7 +958,7 @@ namespace BrightIdeasSoftware
|
||||
public Object Value {
|
||||
get { return this.value; }
|
||||
}
|
||||
private Object value;
|
||||
private Object value = column.GetValue(item.RowObject);
|
||||
|
||||
/// <summary>
|
||||
/// The bounds of the cell that is going to be or has been edited.
|
||||
@@ -975,7 +966,7 @@ namespace BrightIdeasSoftware
|
||||
public Rectangle CellBounds {
|
||||
get { return this.cellBounds; }
|
||||
}
|
||||
private Rectangle cellBounds;
|
||||
private Rectangle cellBounds = cellBounds;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether the control used for editing should be auto matically disposed
|
||||
@@ -1160,25 +1151,22 @@ namespace BrightIdeasSoftware
|
||||
}
|
||||
private SortOrder secondarySortOrder;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// This event is triggered when the contents of a list have changed
|
||||
/// and we want the world to have a chance to filter the list.
|
||||
/// </summary>
|
||||
public class FilterEventArgs : EventArgs
|
||||
/// <remarks>
|
||||
/// Create a FilterEventArgs
|
||||
/// </remarks>
|
||||
/// <param name="objects"></param>
|
||||
public class FilterEventArgs(IEnumerable objects) : EventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Create a FilterEventArgs
|
||||
/// </summary>
|
||||
/// <param name="objects"></param>
|
||||
public FilterEventArgs(IEnumerable objects) {
|
||||
this.Objects = objects;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets what objects are being filtered
|
||||
/// </summary>
|
||||
public IEnumerable Objects;
|
||||
public IEnumerable Objects = objects;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets what objects survived the filtering
|
||||
@@ -1267,17 +1255,13 @@ namespace BrightIdeasSoftware
|
||||
/// <remarks>
|
||||
/// When used with a virtual list, OldObjects will always be null.
|
||||
/// </remarks>
|
||||
public class ItemsChangingEventArgs : CancellableEventArgs
|
||||
/// <remarks>
|
||||
/// Create ItemsChangingEventArgs
|
||||
/// </remarks>
|
||||
/// <param name="oldObjects"></param>
|
||||
/// <param name="newObjects"></param>
|
||||
public class ItemsChangingEventArgs(IEnumerable oldObjects, IEnumerable newObjects) : CancellableEventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Create ItemsChangingEventArgs
|
||||
/// </summary>
|
||||
/// <param name="oldObjects"></param>
|
||||
/// <param name="newObjects"></param>
|
||||
public ItemsChangingEventArgs(IEnumerable oldObjects, IEnumerable newObjects) {
|
||||
this.oldObjects = oldObjects;
|
||||
this.NewObjects = newObjects;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the objects that were in the list before it change.
|
||||
@@ -1286,47 +1270,40 @@ namespace BrightIdeasSoftware
|
||||
public IEnumerable OldObjects {
|
||||
get { return oldObjects; }
|
||||
}
|
||||
private IEnumerable oldObjects;
|
||||
private IEnumerable oldObjects = oldObjects;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the objects that will be in the list after it changes.
|
||||
/// </summary>
|
||||
public IEnumerable NewObjects;
|
||||
public IEnumerable NewObjects = newObjects;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This event is triggered by RemoveObjects before any change has been made to the list.
|
||||
/// </summary>
|
||||
public class ItemsRemovingEventArgs : CancellableEventArgs
|
||||
/// <remarks>
|
||||
/// Create an ItemsRemovingEventArgs
|
||||
/// </remarks>
|
||||
/// <param name="objectsToRemove"></param>
|
||||
public class ItemsRemovingEventArgs(ICollection objectsToRemove) : CancellableEventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Create an ItemsRemovingEventArgs
|
||||
/// </summary>
|
||||
/// <param name="objectsToRemove"></param>
|
||||
public ItemsRemovingEventArgs(ICollection objectsToRemove) {
|
||||
this.ObjectsToRemove = objectsToRemove;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the objects that will be removed
|
||||
/// </summary>
|
||||
public ICollection ObjectsToRemove;
|
||||
public ICollection ObjectsToRemove = objectsToRemove;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Triggered after the user types into a list
|
||||
/// </summary>
|
||||
public class AfterSearchingEventArgs : EventArgs
|
||||
/// <remarks>
|
||||
/// Create an AfterSearchingEventArgs
|
||||
/// </remarks>
|
||||
/// <param name="stringToFind"></param>
|
||||
/// <param name="indexSelected"></param>
|
||||
public class AfterSearchingEventArgs(string stringToFind, int indexSelected) : EventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Create an AfterSearchingEventArgs
|
||||
/// </summary>
|
||||
/// <param name="stringToFind"></param>
|
||||
/// <param name="indexSelected"></param>
|
||||
public AfterSearchingEventArgs(string stringToFind, int indexSelected) {
|
||||
this.stringToFind = stringToFind;
|
||||
this.indexSelected = indexSelected;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the string that was actually searched for
|
||||
@@ -1334,7 +1311,7 @@ namespace BrightIdeasSoftware
|
||||
public string StringToFind {
|
||||
get { return this.stringToFind; }
|
||||
}
|
||||
private string stringToFind;
|
||||
private string stringToFind = stringToFind;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether an the event handler already handled this event
|
||||
@@ -1348,23 +1325,19 @@ namespace BrightIdeasSoftware
|
||||
public int IndexSelected {
|
||||
get { return this.indexSelected; }
|
||||
}
|
||||
private int indexSelected;
|
||||
private int indexSelected = indexSelected;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Triggered when the user types into a list
|
||||
/// </summary>
|
||||
public class BeforeSearchingEventArgs : CancellableEventArgs
|
||||
/// <remarks>
|
||||
/// Create BeforeSearchingEventArgs
|
||||
/// </remarks>
|
||||
/// <param name="stringToFind"></param>
|
||||
/// <param name="startSearchFrom"></param>
|
||||
public class BeforeSearchingEventArgs(string stringToFind, int startSearchFrom) : CancellableEventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Create BeforeSearchingEventArgs
|
||||
/// </summary>
|
||||
/// <param name="stringToFind"></param>
|
||||
/// <param name="startSearchFrom"></param>
|
||||
public BeforeSearchingEventArgs(string stringToFind, int startSearchFrom) {
|
||||
this.StringToFind = stringToFind;
|
||||
this.StartSearchFrom = startSearchFrom;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the string that will be found by the search routine
|
||||
@@ -1372,12 +1345,12 @@ namespace BrightIdeasSoftware
|
||||
/// <remarks>Modifying this value does not modify the memory of what the user has typed.
|
||||
/// When the user next presses a character, the search string will revert to what
|
||||
/// the user has actually typed.</remarks>
|
||||
public string StringToFind;
|
||||
public string StringToFind = stringToFind;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the index of the first row that will be considered to matching.
|
||||
/// </summary>
|
||||
public int StartSearchFrom;
|
||||
public int StartSearchFrom = startSearchFrom;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -2035,27 +2008,20 @@ namespace BrightIdeasSoftware
|
||||
return string.Format("NewHotCellHitLocation: {0}, HotCellHitLocationEx: {1}, NewHotColumnIndex: {2}, NewHotRowIndex: {3}, HotGroup: {4}", this.newHotCellHitLocation, this.hotCellHitLocationEx, this.newHotColumnIndex, this.newHotRowIndex, this.hotGroup);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Let the world know that a checkbox on a subitem is changing
|
||||
/// </summary>
|
||||
public class SubItemCheckingEventArgs : CancellableEventArgs
|
||||
/// <remarks>
|
||||
/// Create a new event block
|
||||
/// </remarks>
|
||||
/// <param name="column"></param>
|
||||
/// <param name="item"></param>
|
||||
/// <param name="subItemIndex"></param>
|
||||
/// <param name="currentValue"></param>
|
||||
/// <param name="newValue"></param>
|
||||
public class SubItemCheckingEventArgs(OLVColumn column, OLVListItem item, int subItemIndex, CheckState currentValue, CheckState newValue) : CancellableEventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Create a new event block
|
||||
/// </summary>
|
||||
/// <param name="column"></param>
|
||||
/// <param name="item"></param>
|
||||
/// <param name="subItemIndex"></param>
|
||||
/// <param name="currentValue"></param>
|
||||
/// <param name="newValue"></param>
|
||||
public SubItemCheckingEventArgs(OLVColumn column, OLVListItem item, int subItemIndex, CheckState currentValue, CheckState newValue) {
|
||||
this.column = column;
|
||||
this.listViewItem = item;
|
||||
this.subItemIndex = subItemIndex;
|
||||
this.currentValue = currentValue;
|
||||
this.newValue = newValue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The column of the cell that is having its checkbox changed.
|
||||
@@ -2063,7 +2029,7 @@ namespace BrightIdeasSoftware
|
||||
public OLVColumn Column {
|
||||
get { return this.column; }
|
||||
}
|
||||
private OLVColumn column;
|
||||
private OLVColumn column = column;
|
||||
|
||||
/// <summary>
|
||||
/// The model object of the row of the cell that is having its checkbox changed.
|
||||
@@ -2078,7 +2044,7 @@ namespace BrightIdeasSoftware
|
||||
public OLVListItem ListViewItem {
|
||||
get { return this.listViewItem; }
|
||||
}
|
||||
private OLVListItem listViewItem;
|
||||
private OLVListItem listViewItem = item;
|
||||
|
||||
/// <summary>
|
||||
/// The current check state of the cell.
|
||||
@@ -2086,7 +2052,7 @@ namespace BrightIdeasSoftware
|
||||
public CheckState CurrentValue {
|
||||
get { return this.currentValue; }
|
||||
}
|
||||
private CheckState currentValue;
|
||||
private CheckState currentValue = currentValue;
|
||||
|
||||
/// <summary>
|
||||
/// The proposed new check state of the cell.
|
||||
@@ -2095,7 +2061,7 @@ namespace BrightIdeasSoftware
|
||||
get { return this.newValue; }
|
||||
set { this.newValue = value; }
|
||||
}
|
||||
private CheckState newValue;
|
||||
private CheckState newValue = newValue;
|
||||
|
||||
/// <summary>
|
||||
/// The index of the cell that is going to be or has been edited.
|
||||
@@ -2103,21 +2069,18 @@ namespace BrightIdeasSoftware
|
||||
public int SubItemIndex {
|
||||
get { return this.subItemIndex; }
|
||||
}
|
||||
private int subItemIndex;
|
||||
private int subItemIndex = subItemIndex;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This event argument block is used when groups are created for a list.
|
||||
/// </summary>
|
||||
public class CreateGroupsEventArgs : EventArgs
|
||||
/// <remarks>
|
||||
/// Create a CreateGroupsEventArgs
|
||||
/// </remarks>
|
||||
/// <param name="parms"></param>
|
||||
public class CreateGroupsEventArgs(GroupingParameters parms) : EventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Create a CreateGroupsEventArgs
|
||||
/// </summary>
|
||||
/// <param name="parms"></param>
|
||||
public CreateGroupsEventArgs(GroupingParameters parms) {
|
||||
this.parameters = parms;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the settings that control the creation of groups
|
||||
@@ -2125,7 +2088,7 @@ namespace BrightIdeasSoftware
|
||||
public GroupingParameters Parameters {
|
||||
get { return this.parameters; }
|
||||
}
|
||||
private GroupingParameters parameters;
|
||||
private GroupingParameters parameters = parms;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the groups that should be used
|
||||
@@ -2151,16 +2114,12 @@ namespace BrightIdeasSoftware
|
||||
/// <summary>
|
||||
/// This event argument block is used when the text of a group task is clicked
|
||||
/// </summary>
|
||||
public class GroupTaskClickedEventArgs : EventArgs
|
||||
/// <remarks>
|
||||
/// Create a GroupTaskClickedEventArgs
|
||||
/// </remarks>
|
||||
/// <param name="group"></param>
|
||||
public class GroupTaskClickedEventArgs(OLVGroup group) : EventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Create a GroupTaskClickedEventArgs
|
||||
/// </summary>
|
||||
/// <param name="group"></param>
|
||||
public GroupTaskClickedEventArgs(OLVGroup group)
|
||||
{
|
||||
this.group = group;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets which group was clicked
|
||||
@@ -2169,7 +2128,7 @@ namespace BrightIdeasSoftware
|
||||
{
|
||||
get { return this.group; }
|
||||
}
|
||||
private readonly OLVGroup group;
|
||||
private readonly OLVGroup group = group;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -2207,18 +2166,13 @@ namespace BrightIdeasSoftware
|
||||
/// <summary>
|
||||
/// This event argument block is used when the state of group has changed (collapsed, selected)
|
||||
/// </summary>
|
||||
public class GroupStateChangedEventArgs : EventArgs {
|
||||
/// <summary>
|
||||
/// Create a GroupStateChangedEventArgs
|
||||
/// </summary>
|
||||
/// <param name="group"></param>
|
||||
/// <param name="oldState"> </param>
|
||||
/// <param name="newState"> </param>
|
||||
public GroupStateChangedEventArgs(OLVGroup group, GroupState oldState, GroupState newState) {
|
||||
this.group = group;
|
||||
this.oldState = oldState;
|
||||
this.newState = newState;
|
||||
}
|
||||
/// <remarks>
|
||||
/// Create a GroupStateChangedEventArgs
|
||||
/// </remarks>
|
||||
/// <param name="group"></param>
|
||||
/// <param name="oldState"> </param>
|
||||
/// <param name="newState"> </param>
|
||||
public class GroupStateChangedEventArgs(OLVGroup group, GroupState oldState, GroupState newState) : EventArgs {
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether the group was collapsed by this event
|
||||
@@ -2291,7 +2245,7 @@ namespace BrightIdeasSoftware
|
||||
get { return this.group; }
|
||||
}
|
||||
|
||||
private readonly OLVGroup group;
|
||||
private readonly OLVGroup group = group;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the previous state of the group
|
||||
@@ -2300,7 +2254,7 @@ namespace BrightIdeasSoftware
|
||||
get { return this.oldState; }
|
||||
}
|
||||
|
||||
private readonly GroupState oldState;
|
||||
private readonly GroupState oldState = oldState;
|
||||
|
||||
|
||||
/// <summary>
|
||||
@@ -2310,7 +2264,7 @@ namespace BrightIdeasSoftware
|
||||
get { return this.newState; }
|
||||
}
|
||||
|
||||
private readonly GroupState newState;
|
||||
private readonly GroupState newState = newState;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -311,18 +311,14 @@ namespace BrightIdeasSoftware
|
||||
/// <remarks>
|
||||
/// Munger uses a chain of these resolve a dotted aspect name.
|
||||
/// </remarks>
|
||||
public class SimpleMunger
|
||||
/// <remarks>
|
||||
/// Create a SimpleMunger
|
||||
/// </remarks>
|
||||
/// <param name="aspectName"></param>
|
||||
public class SimpleMunger(String aspectName)
|
||||
{
|
||||
#region Life and death
|
||||
|
||||
/// <summary>
|
||||
/// Create a SimpleMunger
|
||||
/// </summary>
|
||||
/// <param name="aspectName"></param>
|
||||
public SimpleMunger(String aspectName)
|
||||
{
|
||||
this.aspectName = aspectName;
|
||||
}
|
||||
#region Life and death
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -344,7 +340,7 @@ namespace BrightIdeasSoftware
|
||||
public string AspectName {
|
||||
get { return aspectName; }
|
||||
}
|
||||
private readonly string aspectName;
|
||||
private readonly string aspectName = aspectName;
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -494,19 +490,14 @@ namespace BrightIdeasSoftware
|
||||
/// <summary>
|
||||
/// These exceptions are raised when a munger finds something it cannot process
|
||||
/// </summary>
|
||||
public class MungerException : ApplicationException
|
||||
/// <remarks>
|
||||
/// Create a MungerException
|
||||
/// </remarks>
|
||||
/// <param name="munger"></param>
|
||||
/// <param name="target"></param>
|
||||
/// <param name="ex"></param>
|
||||
public class MungerException(SimpleMunger munger, object target, Exception ex) : ApplicationException("Munger failed", ex)
|
||||
{
|
||||
/// <summary>
|
||||
/// Create a MungerException
|
||||
/// </summary>
|
||||
/// <param name="munger"></param>
|
||||
/// <param name="target"></param>
|
||||
/// <param name="ex"></param>
|
||||
public MungerException(SimpleMunger munger, object target, Exception ex)
|
||||
: base("Munger failed", ex) {
|
||||
this.munger = munger;
|
||||
this.target = target;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the munger that raised the exception
|
||||
@@ -514,7 +505,7 @@ namespace BrightIdeasSoftware
|
||||
public SimpleMunger Munger {
|
||||
get { return munger; }
|
||||
}
|
||||
private readonly SimpleMunger munger;
|
||||
private readonly SimpleMunger munger = munger;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the target that threw the exception
|
||||
@@ -522,7 +513,7 @@ namespace BrightIdeasSoftware
|
||||
public object Target {
|
||||
get { return target; }
|
||||
}
|
||||
private readonly object target;
|
||||
private readonly object target = target;
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -309,12 +309,9 @@ namespace BrightIdeasSoftware
|
||||
/// A default implementation of the IOwnerDataCallback interface
|
||||
/// </summary>
|
||||
[Guid("6FC61F50-80E8-49b4-B200-3F38D3865ABD")]
|
||||
internal class OwnerDataCallbackImpl : IOwnerDataCallback
|
||||
internal class OwnerDataCallbackImpl(VirtualObjectListView olv) : IOwnerDataCallback
|
||||
{
|
||||
public OwnerDataCallbackImpl(VirtualObjectListView olv) {
|
||||
this.olv = olv;
|
||||
}
|
||||
VirtualObjectListView olv;
|
||||
VirtualObjectListView olv = olv;
|
||||
|
||||
#region IOwnerDataCallback Members
|
||||
|
||||
|
||||
@@ -145,20 +145,17 @@ namespace BrightIdeasSoftware
|
||||
/// <summary>
|
||||
/// A do-nothing implementation of the VirtualListDataSource interface.
|
||||
/// </summary>
|
||||
public class AbstractVirtualListDataSource : IVirtualListDataSource, IFilterableDataSource
|
||||
/// <remarks>
|
||||
/// Creates an AbstractVirtualListDataSource
|
||||
/// </remarks>
|
||||
/// <param name="listView"></param>
|
||||
public class AbstractVirtualListDataSource(VirtualObjectListView listView) : IVirtualListDataSource, IFilterableDataSource
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates an AbstractVirtualListDataSource
|
||||
/// </summary>
|
||||
/// <param name="listView"></param>
|
||||
public AbstractVirtualListDataSource(VirtualObjectListView listView) {
|
||||
this.listView = listView;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The list view that this data source is giving information to.
|
||||
/// </summary>
|
||||
protected VirtualObjectListView listView;
|
||||
protected VirtualObjectListView listView = listView;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
@@ -295,15 +292,12 @@ namespace BrightIdeasSoftware
|
||||
/// <summary>
|
||||
/// This class mimics the behavior of VirtualObjectListView v1.x.
|
||||
/// </summary>
|
||||
public class VirtualListVersion1DataSource : AbstractVirtualListDataSource
|
||||
/// <remarks>
|
||||
/// Creates a VirtualListVersion1DataSource
|
||||
/// </remarks>
|
||||
/// <param name="listView"></param>
|
||||
public class VirtualListVersion1DataSource(VirtualObjectListView listView) : AbstractVirtualListDataSource(listView)
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a VirtualListVersion1DataSource
|
||||
/// </summary>
|
||||
/// <param name="listView"></param>
|
||||
public VirtualListVersion1DataSource(VirtualObjectListView listView)
|
||||
: base(listView) {
|
||||
}
|
||||
|
||||
#region Public properties
|
||||
|
||||
|
||||
@@ -369,14 +369,8 @@ namespace BrightIdeasSoftware.Design
|
||||
/// only have to modify the returned collection of actions, but we have to implement
|
||||
/// the properties and commands that the returned actions use. </para>
|
||||
/// </remarks>
|
||||
private class ListViewActionListAdapter : DesignerActionList
|
||||
private class ListViewActionListAdapter(ObjectListViewDesigner designer, DesignerActionList wrappedList) : DesignerActionList(wrappedList.Component)
|
||||
{
|
||||
public ListViewActionListAdapter(ObjectListViewDesigner designer, DesignerActionList wrappedList)
|
||||
: base(wrappedList.Component) {
|
||||
this.designer = designer;
|
||||
this.wrappedList = wrappedList;
|
||||
}
|
||||
|
||||
public override DesignerActionItemCollection GetSortedActionItems() {
|
||||
DesignerActionItemCollection items = wrappedList.GetSortedActionItems();
|
||||
items.RemoveAt(2); // remove Edit Groups
|
||||
@@ -425,21 +419,16 @@ namespace BrightIdeasSoftware.Design
|
||||
set { SetValue(base.Component, "View", value); }
|
||||
}
|
||||
|
||||
ObjectListViewDesigner designer;
|
||||
DesignerActionList wrappedList;
|
||||
ObjectListViewDesigner designer = designer;
|
||||
DesignerActionList wrappedList = wrappedList;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region DesignerCommandSet
|
||||
|
||||
private class CDDesignerCommandSet : DesignerCommandSet
|
||||
private class CDDesignerCommandSet(ComponentDesigner componentDesigner) : DesignerCommandSet
|
||||
{
|
||||
|
||||
public CDDesignerCommandSet(ComponentDesigner componentDesigner) {
|
||||
this.componentDesigner = componentDesigner;
|
||||
}
|
||||
|
||||
public override ICollection GetCommands(string name) {
|
||||
// Debug.WriteLine("CDDesignerCommandSet.GetCommands:" + name);
|
||||
if (componentDesigner != null) {
|
||||
@@ -453,7 +442,7 @@ namespace BrightIdeasSoftware.Design
|
||||
return base.GetCommands(name);
|
||||
}
|
||||
|
||||
private readonly ComponentDesigner componentDesigner;
|
||||
private readonly ComponentDesigner componentDesigner = componentDesigner;
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -463,15 +452,12 @@ namespace BrightIdeasSoftware.Design
|
||||
/// This class works in conjunction with the OLVColumns property to allow OLVColumns
|
||||
/// to be added to the ObjectListView.
|
||||
/// </summary>
|
||||
public class OLVColumnCollectionEditor : System.ComponentModel.Design.CollectionEditor
|
||||
/// <remarks>
|
||||
/// Create a OLVColumnCollectionEditor
|
||||
/// </remarks>
|
||||
/// <param name="t"></param>
|
||||
public class OLVColumnCollectionEditor(Type t) : System.ComponentModel.Design.CollectionEditor(t)
|
||||
{
|
||||
/// <summary>
|
||||
/// Create a OLVColumnCollectionEditor
|
||||
/// </summary>
|
||||
/// <param name="t"></param>
|
||||
public OLVColumnCollectionEditor(Type t)
|
||||
: base(t) {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// What type of object does this editor create?
|
||||
|
||||
@@ -1,15 +1,21 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.WindowsDesktop">
|
||||
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0-windows7.0</TargetFramework>
|
||||
<TargetFramework>net10.0-windows10.0.26100.0</TargetFramework>
|
||||
<Deterministic>false</Deterministic>
|
||||
<RootNamespace>BrightIdeasSoftware</RootNamespace>
|
||||
<AssemblyName>ObjectListView</AssemblyName>
|
||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||
<UseWindowsForms>true</UseWindowsForms>
|
||||
<EnableUnsafeBinaryFormatterSerialization>true</EnableUnsafeBinaryFormatterSerialization>
|
||||
<EnableUnsafeBinaryFormatterSerialization>true</EnableUnsafeBinaryFormatterSerialization>
|
||||
<NoWarn>$(NoWarn);WFO1000</NoWarn>
|
||||
<EnableWinFormsAnalyzers>false</EnableWinFormsAnalyzers>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Platform)' == 'x64'">
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Platform)' == 'ARM64'">
|
||||
<PlatformTarget>ARM64</PlatformTarget>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Include="CustomDictionary.xml" Link="CustomDictionary.xml" />
|
||||
<Content Include="Resources\clear-filter.png" Link="Resources\clear-filter.png" />
|
||||
@@ -19,7 +25,6 @@
|
||||
<Content Include="Resources\sort-ascending.png" Link="Resources\sort-ascending.png" />
|
||||
<Content Include="Resources\sort-descending.png" Link="Resources\sort-descending.png" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="CellEditing\" />
|
||||
<Folder Include="DragDrop\" />
|
||||
@@ -30,9 +35,4 @@
|
||||
<Folder Include="Resources\" />
|
||||
<Folder Include="Rendering\" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="System.Drawing.Common" Version="9.0.2" />
|
||||
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="6.1.0" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
</Project>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -231,15 +231,12 @@ namespace BrightIdeasSoftware {
|
||||
/// This class provides compatibility for v1 RendererDelegates
|
||||
/// </summary>
|
||||
[ToolboxItem(false)]
|
||||
internal class Version1Renderer : AbstractRenderer {
|
||||
public Version1Renderer(RenderDelegate renderDelegate) {
|
||||
this.RenderDelegate = renderDelegate;
|
||||
}
|
||||
internal class Version1Renderer(RenderDelegate renderDelegate) : AbstractRenderer {
|
||||
|
||||
/// <summary>
|
||||
/// The renderer delegate that this renderer wraps
|
||||
/// </summary>
|
||||
public RenderDelegate RenderDelegate;
|
||||
public RenderDelegate RenderDelegate = renderDelegate;
|
||||
|
||||
#region IRenderer Members
|
||||
|
||||
@@ -518,7 +515,7 @@ namespace BrightIdeasSoftware {
|
||||
[Browsable(false),
|
||||
DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
|
||||
public ImageList ImageListOrDefault {
|
||||
get { return this.ImageList ?? this.ListView.SmallImageList; }
|
||||
get { return this.ImageList ?? this.ListView.GetSmallImageList(); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -809,7 +806,7 @@ namespace BrightIdeasSoftware {
|
||||
/// <param name="g"></param>
|
||||
/// <returns></returns>
|
||||
protected virtual Size CalculatePrimaryCheckBoxSize(Graphics g) {
|
||||
if (!this.ListView.CheckBoxes || !this.ColumnIsPrimary)
|
||||
if (!this.ListView.GetCheckBoxes() || !this.ColumnIsPrimary)
|
||||
return Size.Empty;
|
||||
|
||||
Size size = this.CalculateCheckBoxSize(g);
|
||||
@@ -1299,7 +1296,7 @@ namespace BrightIdeasSoftware {
|
||||
int width = 0;
|
||||
|
||||
// Did they hit a check box on the primary column?
|
||||
if (this.ColumnIsPrimary && this.ListView.CheckBoxes) {
|
||||
if (this.ColumnIsPrimary && this.ListView.GetCheckBoxes()) {
|
||||
Size checkBoxSize = this.CalculateCheckBoxSize(g);
|
||||
int checkBoxTop = this.AlignVertically(r, checkBoxSize.Height);
|
||||
Rectangle r3 = new Rectangle(r.X, checkBoxTop, checkBoxSize.Width, checkBoxSize.Height);
|
||||
@@ -1635,7 +1632,7 @@ namespace BrightIdeasSoftware {
|
||||
/// <param name="r">Bounds of the cell</param>
|
||||
protected virtual void DrawImageAndText(Graphics g, Rectangle r) {
|
||||
int offset = 0;
|
||||
if (this.ListView.CheckBoxes && this.ColumnIsPrimary) {
|
||||
if (this.ListView.GetCheckBoxes() && this.ColumnIsPrimary) {
|
||||
offset = this.DrawCheckBox(g, r) + 6;
|
||||
r.X += offset;
|
||||
r.Width -= offset;
|
||||
|
||||
@@ -51,17 +51,20 @@
|
||||
* If you wish to use this code in a closed source application, please contact phillip.piper@gmail.com.
|
||||
*/
|
||||
|
||||
using BrightIdeasSoftware.Properties;
|
||||
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using System.Windows.Forms;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Windows.Forms.VisualStyles;
|
||||
using System.Drawing.Drawing2D;
|
||||
using BrightIdeasSoftware.Properties;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.Versioning;
|
||||
using System.Security.Permissions;
|
||||
using System.Windows.Forms;
|
||||
using System.Windows.Forms.VisualStyles;
|
||||
|
||||
namespace BrightIdeasSoftware
|
||||
{
|
||||
[SupportedOSPlatform("windows")]
|
||||
/// <summary>
|
||||
/// Class used to capture window messages for the header of the list view
|
||||
/// control.
|
||||
@@ -428,7 +431,7 @@ namespace BrightIdeasSoftware
|
||||
/// Override the basic message pump
|
||||
/// </summary>
|
||||
/// <param name="m"></param>
|
||||
[SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.UnmanagedCode)]
|
||||
//[SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.UnmanagedCode)]
|
||||
protected override void WndProc(ref Message m)
|
||||
{
|
||||
const int WM_DESTROY = 2;
|
||||
@@ -489,7 +492,7 @@ namespace BrightIdeasSoftware
|
||||
base.WndProc(ref m);
|
||||
}
|
||||
|
||||
private bool HandleReflectNotify(ref Message m)
|
||||
private static bool HandleReflectNotify(ref Message m)
|
||||
{
|
||||
NativeMethods.NMHDR nmhdr = (NativeMethods.NMHDR)m.GetLParam(typeof(NativeMethods.NMHDR));
|
||||
System.Diagnostics.Debug.WriteLine(String.Format("rn: {0}", nmhdr.code));
|
||||
|
||||
@@ -40,11 +40,13 @@ using System.Collections;
|
||||
using System.ComponentModel;
|
||||
using System.Drawing;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Windows.Forms;
|
||||
using System.Runtime.Versioning;
|
||||
using System.Security.Permissions;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace BrightIdeasSoftware
|
||||
{
|
||||
[SupportedOSPlatform("windows")]
|
||||
/// <summary>
|
||||
/// A limited wrapper around a Windows tooltip window.
|
||||
/// </summary>
|
||||
@@ -643,7 +645,6 @@ namespace BrightIdeasSoftware
|
||||
/// Mess with the basic message pump of the tooltip
|
||||
/// </summary>
|
||||
/// <param name="msg"></param>
|
||||
[SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.UnmanagedCode)]
|
||||
override protected void WndProc(ref Message msg) {
|
||||
//System.Diagnostics.Trace.WriteLine(String.Format("xx {0:x}", msg.Msg));
|
||||
switch (msg.Msg) {
|
||||
@@ -695,5 +696,4 @@ namespace BrightIdeasSoftware
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
}
|
||||
@@ -193,7 +193,7 @@ namespace BrightIdeasSoftware
|
||||
// ReSharper restore DoNotCallOverridableMethodsInConstructor
|
||||
|
||||
// This improves hit detection even if we don't have any state image
|
||||
this.SmallImageList = new ImageList();
|
||||
this.SetSmallImageList(new ImageList());
|
||||
// this.StateImageList.ImageSize = new Size(6, 6);
|
||||
}
|
||||
|
||||
@@ -364,7 +364,7 @@ namespace BrightIdeasSoftware
|
||||
return;
|
||||
|
||||
this.hierarchicalCheckboxes = value;
|
||||
this.CheckBoxes = value;
|
||||
this.SetCheckBoxes(value);
|
||||
if (value)
|
||||
this.TriStateCheckBoxes = false;
|
||||
}
|
||||
@@ -2180,15 +2180,12 @@ namespace BrightIdeasSoftware
|
||||
/// <summary>
|
||||
/// This class sorts branches according to how their respective model objects are sorted
|
||||
/// </summary>
|
||||
public class BranchComparer : IComparer<Branch>
|
||||
/// <remarks>
|
||||
/// Create a BranchComparer
|
||||
/// </remarks>
|
||||
/// <param name="actualComparer"></param>
|
||||
public class BranchComparer(IComparer actualComparer) : IComparer<Branch>
|
||||
{
|
||||
/// <summary>
|
||||
/// Create a BranchComparer
|
||||
/// </summary>
|
||||
/// <param name="actualComparer"></param>
|
||||
public BranchComparer(IComparer actualComparer) {
|
||||
this.actualComparer = actualComparer;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Order the two branches
|
||||
@@ -2200,7 +2197,7 @@ namespace BrightIdeasSoftware
|
||||
return this.actualComparer.Compare(x.Model, y.Model);
|
||||
}
|
||||
|
||||
private readonly IComparer actualComparer;
|
||||
private readonly IComparer actualComparer = actualComparer;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -125,7 +125,7 @@ namespace BrightIdeasSoftware
|
||||
| System.Windows.Forms.AnchorStyles.Left)
|
||||
| System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.objectListView1.CellEditActivation = BrightIdeasSoftware.ObjectListView.CellEditActivateMode.SingleClick;
|
||||
this.objectListView1.CheckBoxes = true;
|
||||
this.objectListView1.SetCheckBoxes(true);
|
||||
this.objectListView1.Columns.AddRange(new System.Windows.Forms.ColumnHeader[] {
|
||||
this.olvColumn1});
|
||||
this.objectListView1.FullRowSelect = true;
|
||||
|
||||
@@ -233,13 +233,9 @@ namespace BrightIdeasSoftware
|
||||
/// A Comparer that will sort a list of columns so that visible ones come before hidden ones,
|
||||
/// and that are ordered by their display order.
|
||||
/// </summary>
|
||||
private class SortByDisplayOrder : IComparer<OLVColumn>
|
||||
private class SortByDisplayOrder(ColumnSelectionForm form) : IComparer<OLVColumn>
|
||||
{
|
||||
public SortByDisplayOrder(ColumnSelectionForm form)
|
||||
{
|
||||
this.Form = form;
|
||||
}
|
||||
private ColumnSelectionForm Form;
|
||||
private ColumnSelectionForm Form = form;
|
||||
|
||||
#region IComparer<OLVColumn> Members
|
||||
|
||||
|
||||
@@ -72,15 +72,12 @@ namespace BrightIdeasSoftware
|
||||
/// }
|
||||
/// </code>
|
||||
/// </example>
|
||||
public class TypedObjectListView<T> where T : class
|
||||
/// <remarks>
|
||||
/// Create a typed wrapper around the given list.
|
||||
/// </remarks>
|
||||
/// <param name="olv">The listview to be wrapped</param>
|
||||
public class TypedObjectListView<T>(ObjectListView olv) where T : class
|
||||
{
|
||||
/// <summary>
|
||||
/// Create a typed wrapper around the given list.
|
||||
/// </summary>
|
||||
/// <param name="olv">The listview to be wrapped</param>
|
||||
public TypedObjectListView(ObjectListView olv) {
|
||||
this.olv = olv;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------
|
||||
// Properties
|
||||
@@ -115,7 +112,7 @@ namespace BrightIdeasSoftware
|
||||
get { return olv; }
|
||||
set { olv = value; }
|
||||
}
|
||||
private ObjectListView olv;
|
||||
private ObjectListView olv = olv;
|
||||
|
||||
/// <summary>
|
||||
/// Get or set the list of all model objects
|
||||
@@ -325,16 +322,13 @@ namespace BrightIdeasSoftware
|
||||
/// A type-safe wrapper around an OLVColumn
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
public class TypedColumn<T> where T : class
|
||||
/// <remarks>
|
||||
/// Creates a TypedColumn
|
||||
/// </remarks>
|
||||
/// <param name="column"></param>
|
||||
public class TypedColumn<T>(OLVColumn column) where T : class
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a TypedColumn
|
||||
/// </summary>
|
||||
/// <param name="column"></param>
|
||||
public TypedColumn(OLVColumn column) {
|
||||
this.column = column;
|
||||
}
|
||||
private OLVColumn column;
|
||||
private OLVColumn column = column;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
|
||||
@@ -77,14 +77,16 @@ using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.Drawing;
|
||||
using System.Reflection;
|
||||
using System.Windows.Forms;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.Versioning;
|
||||
using System.Windows.Forms;
|
||||
|
||||
using MethodInvoker = System.Windows.Forms.MethodInvoker;
|
||||
|
||||
namespace BrightIdeasSoftware
|
||||
{
|
||||
[SupportedOSPlatform("windows")]
|
||||
/// <summary>
|
||||
/// A virtual object list view operates in virtual mode, that is, it only gets model objects for
|
||||
/// a row when it is needed. This gives it the ability to handle very large numbers of rows with
|
||||
@@ -99,7 +101,7 @@ namespace BrightIdeasSoftware
|
||||
/// <para>
|
||||
/// Although it isn't documented, .NET virtual lists cannot have checkboxes. This class codes around this limitation,
|
||||
/// but you must use the functions provided by ObjectListView: CheckedObjects, CheckObject(), UncheckObject() and their friends.
|
||||
/// If you use the normal check box properties (CheckedItems or CheckedIndicies), they will throw an exception, since the
|
||||
/// If you use the normal check box properties (CheckedItems or CheckedIndicie), they will throw an exception, since the
|
||||
/// list is in virtual mode, and .NET "knows" it can't handle checkboxes in virtual mode.
|
||||
/// </para>
|
||||
/// <para>Due to the limits of the underlying Windows control, virtual lists do not trigger ItemCheck/ItemChecked events.
|
||||
@@ -153,7 +155,7 @@ namespace BrightIdeasSoftware
|
||||
/// <para>
|
||||
/// This property returns a simple collection. Changes made to the returned
|
||||
/// collection do NOT affect the list. This is different to the behaviour of
|
||||
/// CheckedIndicies collection.
|
||||
/// CheckedIndicie collection.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// When getting CheckedObjects, the performance of this method is O(n) where n is the number of checked objects.
|
||||
@@ -175,7 +177,7 @@ namespace BrightIdeasSoftware
|
||||
public override IList CheckedObjects {
|
||||
get {
|
||||
// If we aren't should checkboxes, then no objects can be checked
|
||||
if (!this.CheckBoxes)
|
||||
if (!this.GetCheckBoxes())
|
||||
return new ArrayList();
|
||||
|
||||
// If the data source has somehow vanished, we can't do anything
|
||||
@@ -199,7 +201,7 @@ namespace BrightIdeasSoftware
|
||||
return objects;
|
||||
}
|
||||
set {
|
||||
if (!this.CheckBoxes)
|
||||
if (!this.GetCheckBoxes())
|
||||
return;
|
||||
|
||||
// If a custom check state getter is install, we can't use our check state management
|
||||
@@ -403,8 +405,6 @@ namespace BrightIdeasSoftware
|
||||
[UnsafeAccessor(UnsafeAccessorKind.Field, Name = "_virtualListSize")]
|
||||
private static extern ref int GetVirtualListSizeField(ListView listView);
|
||||
|
||||
static private FieldInfo virtualListSizeFieldInfo;
|
||||
|
||||
#endregion
|
||||
|
||||
#region OLV accessing
|
||||
|
||||
67
PANEL_BINDING_FEATURE.md
Normal file
67
PANEL_BINDING_FEATURE.md
Normal file
@@ -0,0 +1,67 @@
|
||||
# Panel Binding Feature
|
||||
|
||||
## Overview
|
||||
This feature allows users to bind the Connections and Config panels together when they are in auto-hide state (collapsed). When one panel is clicked to expand, the other panel will automatically expand as well.
|
||||
|
||||
## How It Works
|
||||
|
||||
### User Workflow
|
||||
1. The user collapses both the Connections and Config panels by clicking the auto-hide pin icon (they become auto-hidden tabs on the left side)
|
||||
2. The user enables the "Bind Connections and Config panels together when auto-hidden" option in Tools > Options > Tabs & Panels
|
||||
3. When the user clicks on the Connections tab to expand it, the Config panel will automatically expand as well
|
||||
4. Similarly, when clicking on the Config tab, the Connections panel will expand
|
||||
5. Both panels stay expanded together, allowing the user to view connection settings easily
|
||||
6. When the user clicks away from the panels, both collapse back to auto-hide
|
||||
|
||||
### Benefits
|
||||
- Reduces the number of clicks needed to view and edit connection settings
|
||||
- Panels work together seamlessly when in auto-hide mode
|
||||
- User can still use panels independently when they are pinned (docked)
|
||||
- Configurable option allows users to enable/disable as needed
|
||||
|
||||
## Implementation Details
|
||||
|
||||
### Files Modified
|
||||
1. **Properties/OptionsTabsPanelsPage.settings** - Added `BindConnectionsAndConfigPanels` setting (default: false)
|
||||
2. **Properties/OptionsTabsPanelsPage.Designer.cs** - Added property accessor for the new setting
|
||||
3. **UI/Panels/PanelBinder.cs** - NEW - Core logic for binding panel visibility
|
||||
4. **UI/Forms/OptionsPages/TabsPanelsPage.cs** - Added checkbox and load/save logic
|
||||
5. **UI/Forms/OptionsPages/TabsPanelsPage.Designer.cs** - Added UI checkbox control
|
||||
6. **UI/Forms/frmMain.cs** - Initialize PanelBinder after panels are loaded
|
||||
7. **Config/Settings/Registry/OptRegistryTabsPanelsPage.cs** - Added registry support for enterprise deployment
|
||||
|
||||
### Key Classes
|
||||
|
||||
#### PanelBinder
|
||||
- Singleton class that manages the binding between panels
|
||||
- Subscribes to VisibleChanged events on both TreeForm (Connections) and ConfigForm (Config)
|
||||
- Only acts when:
|
||||
- The binding setting is enabled
|
||||
- Both panels are in auto-hide state
|
||||
- One panel becomes visible (user clicked its tab)
|
||||
- Uses a `_isProcessing` flag to prevent recursive event triggers
|
||||
- Calls `Activate()` on the other panel to show it
|
||||
|
||||
### How to Test
|
||||
|
||||
1. Build and run mRemoteNG
|
||||
2. Go to Tools > Options > Tabs & Panels
|
||||
3. Verify the new checkbox "Bind Connections and Config panels together when auto-hidden" is present
|
||||
4. Create a test connection in the Connections panel
|
||||
5. Auto-hide both the Connections and Config panels (click the pin icon on each)
|
||||
6. Both panels should now appear as collapsed tabs on the left side
|
||||
7. Enable the binding option in Options
|
||||
8. Click on the Connections tab - both Connections and Config should expand
|
||||
9. Click away from the panels - both should collapse
|
||||
10. Click on the Config tab - both panels should expand again
|
||||
11. Disable the binding option
|
||||
12. Verify panels now work independently when clicking their tabs
|
||||
13. Pin one or both panels (dock them)
|
||||
14. Verify the binding only works when BOTH panels are in auto-hide state
|
||||
|
||||
## Registry Support
|
||||
|
||||
Administrators can configure this setting via registry for enterprise deployment:
|
||||
- Key: `HKEY_LOCAL_MACHINE\SOFTWARE\mRemoteNG\TabsAndPanels` or `HKEY_CURRENT_USER\SOFTWARE\mRemoteNG\TabsAndPanels`
|
||||
- Value: `BindConnectionsAndConfigPanels` (DWORD)
|
||||
- 0 = Disabled, 1 = Enabled
|
||||
42
README.md
42
README.md
@@ -1,6 +1,18 @@
|
||||
**NOTICE: This project currently transited to a new maintainer. Development help would be greatly appreciated.**
|
||||
<p align="Left">
|
||||
Developing mRemoteNG to its fullest potential is my personal priority.<br>
|
||||
While the project remains non-commercial, it does come with ongoing costs — including VPS hosting for testing, AI tools, domain fees, and more. <br> If you find value in mRemoteNG and want to support its future, even a small donation from our community can make a huge difference.<br>
|
||||
Your support helps me keep the project secure, modern, and accessible for everyone who relies on it — and brings us closer to a brighter, more collaborative future.<br><br>
|
||||
Consider donating — every contribution counts!
|
||||
<br><br>
|
||||
<a href="https://www.paypal.com/paypalme/mremoteng">
|
||||
<img height='36' alt="PayPal" style='border:0px;height:36px;' src="https://img.shields.io/badge/%24-PayPal-blue.svg?label=Donate&logo=PayPal&style=flat-square">
|
||||
</a><br>
|
||||
<a href='https://ko-fi.com/Q5Q41I7JS' target='_blank'><img height='36' style='border:0px;height:36px;' src='https://storage.ko-fi.com/cdn/kofi6.png?v=6' border='0' alt='Buy Me a Coffee at ko-fi.com' /></a>
|
||||
</a>
|
||||
</p>
|
||||
|
||||
---
|
||||
|
||||
<br/><br/>
|
||||
<p align="center">
|
||||
<img width="450" src="https://github.com/mRemoteNG/mRemoteNG/blob/mRemoteNGProjectFiles/Header_dark.png">
|
||||
</p>
|
||||
@@ -24,14 +36,6 @@
|
||||
<img alt="Element" src="https://img.shields.io/matrix/mremoteng:matrix.org?label=Join%20to%20chat%20about%20mRemoteNG&logo=element&style=social&link=https://app.element.io/#/room/#mremoteng:matrix.org">
|
||||
</a>
|
||||
</p>
|
||||
<p align="center">
|
||||
<a href="https://www.paypal.com/paypalme/mremoteng">
|
||||
<img alt="PayPal" src="https://img.shields.io/badge/%24-PayPal-blue.svg?label=Donate&logo=PayPal&style=flat-square">
|
||||
</a>
|
||||
<a href="bitcoin:16fUnHUM3k7W9Fvpc6dug7TAdfeGEcLbSg">
|
||||
<img alt="Bitcoin" src="https://img.shields.io/badge/%24-Bitcoin.svg?label=Donate&logo=bitcoin&style=flat-square">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/mRemoteNG/mRemoteNG/blob/develop/COPYING.TXT">
|
||||
@@ -54,7 +58,7 @@
|
||||
| ---------------|--------------|-----------|
|
||||
| Stable |  | [](https://github.com/mRemoteNG/mRemoteNG/releases/tag/v1.76.20) |
|
||||
| Preview |  | [](https://github.com/mRemoteNG/mRemoteNG/releases/tag/v1.77.1) |
|
||||
| Nightly |  | [](https://github.com/mRemoteNG/mRemoteNG/releases/tag/2023.03.03-v1.77.3-nb) |
|
||||
| Nightly |  | [/total.svg)](https://github.com/mRemoteNG/mRemoteNG/releases/tag/20250916-v1.78.2-NB-(3177)) |
|
||||
|
||||
## Features
|
||||
|
||||
@@ -68,6 +72,7 @@ The following protocols are supported:
|
||||
* rlogin (Remote Login)
|
||||
* Raw Socket Connections
|
||||
* Powershell remoting
|
||||
* AnyDesk
|
||||
|
||||
For a detailed feature list and general usage support, refer to the [Documentation](https://mremoteng.readthedocs.io/en/latest/).
|
||||
|
||||
@@ -85,15 +90,19 @@ For a detailed feature list and general usage support, refer to the [Documentati
|
||||
|
||||
#### Source package
|
||||
|
||||
This contains the source code from which mRemoteNG is build.
|
||||
This contains the source code from which mRemoteNG is built.
|
||||
You will need to compile it yourself using Visual Studio.
|
||||
|
||||
### Minimum Requirements
|
||||
|
||||
* [Microsoft Visual C++ Redistributable for Visual Studio 2015 - 2022](https://support.microsoft.com/en-us/help/2977003/the-latest-supported-visual-c-downloads)
|
||||
* [Microsoft .NET 6.0 Desktop Runtime](https://dotnet.microsoft.com/download/dotnet/6.0)
|
||||
* Microsoft Terminal Service Client 6.0 or later
|
||||
* Needed if you use RDP. mstscax.dll and/or msrdp.ocx be registered.
|
||||
Make sure you have the latest version installed:
|
||||
|
||||
* [Microsoft .NET Desktop Runtime 10.0](https://dotnet.microsoft.com/download/dotnet/10.0)
|
||||
* Microsoft Visual C++ Redistributable 2015–2026 is needed:
|
||||
- [x64](https://aka.ms/vs/18/release/vc_redist.x64.exe)
|
||||
- [ARM64](https://aka.ms/vs/18/release/vc_redist.arm64.exe)
|
||||
- [x86](https://aka.ms/vs/18/release/vc_redist.x86.exe)
|
||||
* Microsoft Terminal Service Client 6.0 or later (needed if you use RDP with mstscax.dll and/or msrdp.ocx to be registered)
|
||||
|
||||
### Download
|
||||
|
||||
@@ -137,6 +146,7 @@ _If you are using the Portable version, simply deleting the folder that contains
|
||||
|
||||
* [PSmRemoteNG](https://github.com/realslacker/PSmRemoteNG) A module to create mRemoteNG connection files from PowerShell.
|
||||
* [mRemoteNGOpenVPN](https://github.com/T3los/mRemoteNGOpenVPN) A script that can be embedded as an external tool to control OpenVPN.
|
||||
* [mRemoteNG-Icons](https://github.com/bearlikelion/mRemoteNG-Icons) A collection of fancy icons to customize the connections
|
||||
|
||||
## Contribute
|
||||
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
U2FsdGVkX1/uEeToOEIrunpoPNl7NUYNQfI+ixMzGgKX0DlHZxa/PonAVd9NoAE+
|
||||
dqWegkN8fa/M2HcW8moN5sN0yS88amG8cfwRZNRSgRB4IxDdYPjM/iX7y8FjUh9R
|
||||
OZnGhq1rAkqcf7ASAdZfQDSFOAEDPQgByN4IB2j/G3ueqV3jf5sWWiM0ielcorWX
|
||||
jXuEL7uIk9diI3Qh3BVqmrotzErzqTTLB4DWoL1aWqRRYH+exKahfcMT3C9r+tul
|
||||
ETH6o1im3kdYzFgHl58FmihbHa+h1+c+DWVGGXRxJPw1DZfg8/+ldIy8J75aWimz
|
||||
2MX+PiBVQj+wYlSSkQLj2EdbpSERlinE1O55ldwpbnMPAlYCgFRdO3/hiB6LiLLt
|
||||
n9s8f32HLNG20Mk1oxXdN9VPOw+RwQpUf6Zfcyps6e/9EFKBLnwq4cYwM/dHRuez
|
||||
w48EJX5wKzpukHn2UFg5aCRGYU/4NyUn+AlIjjCMBWY1R0uBhC340cl6YqoPFN12
|
||||
clzbS6JdQS+ktusCeaKcsj2iknVqQrGY81LKrTaQZdfehGwAn9pMWE3iWRX80yzO
|
||||
s08lpmHfPK4uhtlyIbdReLn4n7hvB8KRVa7Foms+4wpEwKrL8KF/8CYc89Tm3PSD
|
||||
LCrLr7rzCHh51ncJXHAwZqY2ZRLnQBVwhIRsFYyZ595b89tuDY00sYpWnTKzWubK
|
||||
UD993CmYKg1cA+mj6NR6iSmecawsUz68nI/nmHM2APE5cCCHSK3lz40e9Z9Hmy5Y
|
||||
kVS6c86a+gwNyn4F9t3iISdj2vIOwGMVWQLeY+nwpiKnnOuI0XPtIMjNbhoaDToT
|
||||
kph2ZVjqbpmYHgTSX71v8Y16Stl+p/dZn0dE2d4JhxOJxDN//H6y7mOwg8DYX9GP
|
||||
rFBMIEWyEQyEFYTmWgztQ5KU89w0lV1qiWpaiWR/IDkGbUzHR8ELp4NHYPbZnuYa
|
||||
FFXooJGPsQ02ioXPm18Lkhloo63lANgyBE0jc1x9hPrI0oVn1wTkKdeW13IbHzrn
|
||||
VsGXeZz06bB46QChXUPmQ49sNkAXcMDnfNFM9ayUa8eVmNx9fWabtBy8qs7rpdv0
|
||||
1y/2ud4Da9t7SAUvxwI65+b2Ytk05pLFLmQYb01g4BpBDxbJW3yw3CqKgMnLoZ3t
|
||||
s2kiUpn7FEQU8jbpbFV+UB4DdJgm8S7o1gbTEWYTscZ7l+oLCmQgMYoi/EJDDIa2
|
||||
cCSdGy4p7kLlLUoO438Mz36+FDf6qX2B86ezVdNnXQb3UPljjDxiOFM6NkI3HTpl
|
||||
RqxjBg8JdrwoQ1UMha6ucDKhPXq1xkg0dpO9QyjxywssG3krgQ5Py64s5Xu7IgQm
|
||||
AzmwGTZ6gFZlDTr9SpJkiucF9vexCo6JHHkF8OjhS49FanqB8otJvg5JclVugP51
|
||||
LcqqvuMkAsFago261SNcOhgtR6nV5B9QgHyr66c6YnTlwt07T1Qq3S1lw2x0Eccs
|
||||
PKbJDVU5rAHiM71QYmwsuoC8qkYPTtVPIoUTs+5u5aLywVoLejr1dE5twNXy5PYY
|
||||
fDwubg0YG3kchvv85N3epZ1h50ADq3W3msU9bWDKWwdwIbpGq+dwjkLssBQmjVtI
|
||||
R8rGbt1DgL7xtRNF9ESnWVkfHJvJu/5oD6wGAU/oIfBxVON0VYb1evc8wRdQTbDH
|
||||
Dnt+aKwcSPYdyGVRKfRtBGvEZ8rB5hzCXQnS795L0imdfXjBSJn7Pnl9VwpcB3Pr
|
||||
aZ6s0GcB8kYDEXzjv+o7JF6k5i2I+sVGwvFVGIoVd/Igq2ysrk/GfWVov0SUu78A
|
||||
JeHYdtRuKwXOdZw2cjZQ72bvFaHOuoXrQnyKyZDWRyu0NB5HLW75v/YEbr4msIm9
|
||||
E+3HFwRvKSTfUx/M4NgVKrgsHDeBRD4tLNx/SerQvqaplunM7OfAtULucveUhwSo
|
||||
vT6uNK3URe1qDgX554cz8c6+KrkglTLFMuKfNWj3q/uSM0BxTTD9QgorNdlMmErH
|
||||
TfV/ZpZACpuMFRbC5xQRkCG1x4U12pdPbtIkGtVBJROEXhP1aw3BDWwrIN7zSgfj
|
||||
8I4OF4fbj/rSuvI4klzi1zlUMQQnenlRURE+7DKRxtWipJhW9vtDI0LXN7gGfmbR
|
||||
73uR3YUny6zUJ0svqaWj4Eo6t3g99nmk4D2hm/622dRksv2HqEQiq29jJxlcbdZB
|
||||
PU96wOp54s/BzgodyI5dh+xL06Obr9AltLV9vw3iK0VBZqquj9FuhvWC1tYlZQJF
|
||||
AOgVDOGUZzmAJXftI7gaohFBwsT5tAHQtBuY7tB3b1hrnfrFb9FTNxGZJKcIH3p4
|
||||
dOAvedsfuq+2/lU9iM4tX9fjSzfnGRZRuKCGDSdhE6EDik9/f2kSCoY9z0zJwdZH
|
||||
324TpGbZNbgcwgHDL9i5Nsnaua5yxtr0/Fr1We1tvn0=
|
||||
122
VISUAL_EXAMPLES.md
Normal file
122
VISUAL_EXAMPLES.md
Normal file
@@ -0,0 +1,122 @@
|
||||
# Connection Frame Color - Visual Examples
|
||||
|
||||
## Before and After
|
||||
|
||||
### Without Frame Color (Default)
|
||||
```
|
||||
┌──────────────────────────────────────────────┐
|
||||
│ Connection Tab │
|
||||
├──────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ │
|
||||
│ [Normal connection content area] │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
└──────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### With Red Frame Color (Production)
|
||||
```
|
||||
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
||||
┃ Connection Tab (Production) ┃
|
||||
┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
|
||||
┃ ╔════════════════════════════════════════╗ ┃
|
||||
┃ ║ ║ ┃
|
||||
┃ ║ [Connection content area] ║ ┃
|
||||
┃ ║ With 4-pixel RED border all around ║ ┃
|
||||
┃ ║ ║ ┃
|
||||
┃ ╚════════════════════════════════════════╝ ┃
|
||||
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
||||
```
|
||||
|
||||
### With Green Frame Color (Test)
|
||||
```
|
||||
┌──────────────────────────────────────────────┐
|
||||
│ Connection Tab (Test) │
|
||||
├──────────────────────────────────────────────┤
|
||||
│ ╔════════════════════════════════════════╗ │
|
||||
│ ║ ║ │
|
||||
│ ║ [Connection content area] ║ │
|
||||
│ ║ With 4-pixel GREEN border all around ║ │
|
||||
│ ║ ║ │
|
||||
│ ╚════════════════════════════════════════╝ │
|
||||
└──────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Side-by-Side Comparison
|
||||
|
||||
```
|
||||
Test Connection Production Connection
|
||||
┌─────────────────────┐ ┏━━━━━━━━━━━━━━━━━━━━━┓
|
||||
│ [test server] │ ┃ [production server] ┃
|
||||
│ │ ┃ ╔═══════════════╗ ┃
|
||||
│ ┌───────────────┐ │ ┃ ║ ║ ┃
|
||||
│ │ │ │ ┃ ║ !WARNING! ║ ┃
|
||||
│ │ SSH Session │ │ ┃ ║ Production ║ ┃
|
||||
│ │ (Green) │ │ ┃ ║ Environment ║ ┃
|
||||
│ └───────────────┘ │ ┃ ║ (Red) ║ ┃
|
||||
│ │ ┃ ╚═══════════════╝ ┃
|
||||
└─────────────────────┘ ┗━━━━━━━━━━━━━━━━━━━━━┛
|
||||
Safe to experiment Requires extra caution!
|
||||
```
|
||||
|
||||
## Property Grid Display
|
||||
|
||||
When you select a connection in mRemoteNG, the Config panel will show:
|
||||
|
||||
```
|
||||
┌─ Display ──────────────────────────────────┐
|
||||
│ │
|
||||
│ Name: My Server │
|
||||
│ Description: Production DB │
|
||||
│ Icon: SSH │
|
||||
│ Panel: General │
|
||||
│ Color: [empty] │
|
||||
│ Tab Color: [empty] │
|
||||
│ Connection Frame Color: ▼ Red (Production)│
|
||||
│ ├─ None │
|
||||
│ ├─ Red (Prod...)│
|
||||
│ ├─ Yellow (St...)│
|
||||
│ ├─ Green (Test) │
|
||||
│ ├─ Blue (Dev) │
|
||||
│ └─ Purple (C...)│
|
||||
└────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Folder Inheritance Example
|
||||
|
||||
```
|
||||
📁 Production Servers (ConnectionFrameColor: Red)
|
||||
├─ 📄 Web Server 1 (inherits Red)
|
||||
├─ 📄 Web Server 2 (inherits Red)
|
||||
└─ 📄 Database Server (inherits Red)
|
||||
|
||||
📁 Development Servers (ConnectionFrameColor: Blue)
|
||||
├─ 📄 Dev Server 1 (inherits Blue)
|
||||
└─ 📄 Dev Server 2 (inherits Blue)
|
||||
|
||||
📁 Test Servers (ConnectionFrameColor: Green)
|
||||
├─ 📄 Test Server 1 (inherits Green)
|
||||
└─ 📄 QA Server (inherits Green)
|
||||
```
|
||||
|
||||
All connections in the "Production Servers" folder will automatically
|
||||
get a red border when you connect to them (assuming inheritance is enabled).
|
||||
|
||||
## Color Palette
|
||||
|
||||
The implementation uses these specific colors:
|
||||
|
||||
- **Red (Production)**: RGB(220, 53, 69) - High visibility warning color
|
||||
- **Yellow (Staging/UAT)**: RGB(255, 193, 7) - Caution/warning color
|
||||
- **Green (Test)**: RGB(40, 167, 69) - Safe/go color
|
||||
- **Blue (Development)**: RGB(0, 123, 255) - Calm, neutral color
|
||||
- **Purple (Custom)**: RGB(111, 66, 193) - Distinct custom color
|
||||
- **None (Default)**: Transparent - No border
|
||||
|
||||
These colors are chosen for:
|
||||
1. High contrast and visibility
|
||||
2. Universal recognition (red = danger, green = safe)
|
||||
3. Accessibility considerations
|
||||
4. Consistency with modern UI design patterns
|
||||
@@ -1,80 +1,64 @@
|
||||
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.0.31912.275
|
||||
MinimumVisualStudioVersion = 14.0.25420.1
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "mRemoteNG", "mRemoteNG\mRemoteNG.csproj", "{4934A491-40BC-4E5B-9166-EA1169A220F6}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "mRemoteNGTests", "mRemoteNGTests\mRemoteNGTests.csproj", "{1453B37F-8621-499E-B0B2-6091F76DC0BB}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "mRemoteNGInstaller", "mRemoteNGInstaller", "{4FE795BE-646E-4F1B-BAD0-A68EA26394DD}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CustomActions", "mRemoteNGInstaller\CustomActions\CustomActions.csproj", "{5423D985-CB48-4344-B47F-E8C6D60C8B04}"
|
||||
EndProject
|
||||
Project("{930C7802-8A8C-48F9-8165-68863BCCD9DD}") = "Installer", "mRemoteNGInstaller\Installer\Installer.wixproj", "{F0168B9F-6815-40DF-BA53-46CEE7683B68}"
|
||||
ProjectSection(ProjectDependencies) = postProject
|
||||
{4934A491-40BC-4E5B-9166-EA1169A220F6} = {4934A491-40BC-4E5B-9166-EA1169A220F6}
|
||||
{5423D985-CB48-4344-B47F-E8C6D60C8B04} = {5423D985-CB48-4344-B47F-E8C6D60C8B04}
|
||||
{A56A2029-79B8-492A-ABE5-D2BFE05801BD} = {A56A2029-79B8-492A-ABE5-D2BFE05801BD}
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "mRemoteNGSpecs", "mRemoteNGSpecs\mRemoteNGSpecs.csproj", "{16AA21E2-D6B7-427D-AB7D-AA8C611B724E}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ExternalConnectors", "ExternalConnectors\ExternalConnectors.csproj", "{A56A2029-79B8-492A-ABE5-D2BFE05801BD}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ObjectListView.NetCore", "ObjectListView\ObjectListView.NetCore.csproj", "{5718734C-03AC-4954-89B1-1723CF03AF10}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|arm64 = Debug|arm64
|
||||
Debug|x64 = Debug|x64
|
||||
Release Installer and Portable|x64 = Release Installer and Portable|x64
|
||||
Release Self-Contained|arm64 = Release Self-Contained|arm64
|
||||
Release Self-Contained|x64 = Release Self-Contained|x64
|
||||
Release|arm64 = Release|arm64
|
||||
Release|x64 = Release|x64
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{4934A491-40BC-4E5B-9166-EA1169A220F6}.Debug|arm64.ActiveCfg = Debug|arm64
|
||||
{4934A491-40BC-4E5B-9166-EA1169A220F6}.Debug|arm64.Build.0 = Debug|arm64
|
||||
{4934A491-40BC-4E5B-9166-EA1169A220F6}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{4934A491-40BC-4E5B-9166-EA1169A220F6}.Debug|x64.Build.0 = Debug|x64
|
||||
{4934A491-40BC-4E5B-9166-EA1169A220F6}.Release Installer and Portable|x64.ActiveCfg = Release Portable|x64
|
||||
{4934A491-40BC-4E5B-9166-EA1169A220F6}.Release Installer and Portable|x64.Build.0 = Release Portable|x64
|
||||
{4934A491-40BC-4E5B-9166-EA1169A220F6}.Release Self-Contained|arm64.ActiveCfg = Release Self-Contained|arm64
|
||||
{4934A491-40BC-4E5B-9166-EA1169A220F6}.Release Self-Contained|arm64.Build.0 = Release Self-Contained|arm64
|
||||
{4934A491-40BC-4E5B-9166-EA1169A220F6}.Release Self-Contained|x64.ActiveCfg = Release Self-Contained|x64
|
||||
{4934A491-40BC-4E5B-9166-EA1169A220F6}.Release Self-Contained|x64.Build.0 = Release Self-Contained|x64
|
||||
{4934A491-40BC-4E5B-9166-EA1169A220F6}.Release|arm64.ActiveCfg = Release|arm64
|
||||
{4934A491-40BC-4E5B-9166-EA1169A220F6}.Release|arm64.Build.0 = Release|arm64
|
||||
{4934A491-40BC-4E5B-9166-EA1169A220F6}.Release|x64.ActiveCfg = Release|x64
|
||||
{4934A491-40BC-4E5B-9166-EA1169A220F6}.Release|x64.Build.0 = Release|x64
|
||||
{1453B37F-8621-499E-B0B2-6091F76DC0BB}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{1453B37F-8621-499E-B0B2-6091F76DC0BB}.Debug|x64.Build.0 = Debug|x64
|
||||
{1453B37F-8621-499E-B0B2-6091F76DC0BB}.Release Installer and Portable|x64.ActiveCfg = Release Portable|x64
|
||||
{1453B37F-8621-499E-B0B2-6091F76DC0BB}.Release|x64.ActiveCfg = Release|x64
|
||||
{5423D985-CB48-4344-B47F-E8C6D60C8B04}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{5423D985-CB48-4344-B47F-E8C6D60C8B04}.Debug|x64.Build.0 = Debug|x64
|
||||
{5423D985-CB48-4344-B47F-E8C6D60C8B04}.Release Installer and Portable|x64.ActiveCfg = Release|x64
|
||||
{5423D985-CB48-4344-B47F-E8C6D60C8B04}.Release Installer and Portable|x64.Build.0 = Release|x64
|
||||
{5423D985-CB48-4344-B47F-E8C6D60C8B04}.Release|x64.ActiveCfg = Release|x64
|
||||
{F0168B9F-6815-40DF-BA53-46CEE7683B68}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{F0168B9F-6815-40DF-BA53-46CEE7683B68}.Debug|x64.Build.0 = Debug|x64
|
||||
{F0168B9F-6815-40DF-BA53-46CEE7683B68}.Release Installer and Portable|x64.ActiveCfg = Release|x64
|
||||
{F0168B9F-6815-40DF-BA53-46CEE7683B68}.Release Installer and Portable|x64.Build.0 = Release|x64
|
||||
{F0168B9F-6815-40DF-BA53-46CEE7683B68}.Release|x64.ActiveCfg = Release|x64
|
||||
{16AA21E2-D6B7-427D-AB7D-AA8C611B724E}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{16AA21E2-D6B7-427D-AB7D-AA8C611B724E}.Debug|x64.Build.0 = Debug|x64
|
||||
{16AA21E2-D6B7-427D-AB7D-AA8C611B724E}.Release Installer and Portable|x64.ActiveCfg = Release Installer|x64
|
||||
{16AA21E2-D6B7-427D-AB7D-AA8C611B724E}.Release|x64.ActiveCfg = Release|x64
|
||||
{A56A2029-79B8-492A-ABE5-D2BFE05801BD}.Debug|arm64.ActiveCfg = Debug|arm64
|
||||
{A56A2029-79B8-492A-ABE5-D2BFE05801BD}.Debug|arm64.Build.0 = Debug|arm64
|
||||
{A56A2029-79B8-492A-ABE5-D2BFE05801BD}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{A56A2029-79B8-492A-ABE5-D2BFE05801BD}.Debug|x64.Build.0 = Debug|x64
|
||||
{A56A2029-79B8-492A-ABE5-D2BFE05801BD}.Release Installer and Portable|x64.ActiveCfg = Release Portable|x64
|
||||
{A56A2029-79B8-492A-ABE5-D2BFE05801BD}.Release Installer and Portable|x64.Build.0 = Release Portable|x64
|
||||
{A56A2029-79B8-492A-ABE5-D2BFE05801BD}.Release Self-Contained|arm64.ActiveCfg = Release Self-Contained|arm64
|
||||
{A56A2029-79B8-492A-ABE5-D2BFE05801BD}.Release Self-Contained|arm64.Build.0 = Release Self-Contained|arm64
|
||||
{A56A2029-79B8-492A-ABE5-D2BFE05801BD}.Release Self-Contained|x64.ActiveCfg = Release Self-Contained|x64
|
||||
{A56A2029-79B8-492A-ABE5-D2BFE05801BD}.Release Self-Contained|x64.Build.0 = Release Self-Contained|x64
|
||||
{A56A2029-79B8-492A-ABE5-D2BFE05801BD}.Release|arm64.ActiveCfg = Release|arm64
|
||||
{A56A2029-79B8-492A-ABE5-D2BFE05801BD}.Release|arm64.Build.0 = Release|arm64
|
||||
{A56A2029-79B8-492A-ABE5-D2BFE05801BD}.Release|x64.ActiveCfg = Release|x64
|
||||
{A56A2029-79B8-492A-ABE5-D2BFE05801BD}.Release|x64.Build.0 = Release|x64
|
||||
{5718734C-03AC-4954-89B1-1723CF03AF10}.Debug|arm64.ActiveCfg = Debug|Any CPU
|
||||
{5718734C-03AC-4954-89B1-1723CF03AF10}.Debug|arm64.Build.0 = Debug|Any CPU
|
||||
{5718734C-03AC-4954-89B1-1723CF03AF10}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{5718734C-03AC-4954-89B1-1723CF03AF10}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{5718734C-03AC-4954-89B1-1723CF03AF10}.Release Installer and Portable|x64.ActiveCfg = Release|Any CPU
|
||||
{5718734C-03AC-4954-89B1-1723CF03AF10}.Release Installer and Portable|x64.Build.0 = Release|Any CPU
|
||||
{5718734C-03AC-4954-89B1-1723CF03AF10}.Release Self-Contained|arm64.ActiveCfg = Release|Any CPU
|
||||
{5718734C-03AC-4954-89B1-1723CF03AF10}.Release Self-Contained|arm64.Build.0 = Release|Any CPU
|
||||
{5718734C-03AC-4954-89B1-1723CF03AF10}.Release Self-Contained|x64.ActiveCfg = Release|Any CPU
|
||||
{5718734C-03AC-4954-89B1-1723CF03AF10}.Release Self-Contained|x64.Build.0 = Release|Any CPU
|
||||
{5718734C-03AC-4954-89B1-1723CF03AF10}.Release|arm64.ActiveCfg = Release|Any CPU
|
||||
{5718734C-03AC-4954-89B1-1723CF03AF10}.Release|arm64.Build.0 = Release|Any CPU
|
||||
{5718734C-03AC-4954-89B1-1723CF03AF10}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{5718734C-03AC-4954-89B1-1723CF03AF10}.Release|x64.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(NestedProjects) = preSolution
|
||||
{5423D985-CB48-4344-B47F-E8C6D60C8B04} = {4FE795BE-646E-4F1B-BAD0-A68EA26394DD}
|
||||
{F0168B9F-6815-40DF-BA53-46CEE7683B68} = {4FE795BE-646E-4F1B-BAD0-A68EA26394DD}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {5D390A0C-2FC4-4908-B86E-7E4DEF5916EC}
|
||||
EndGlobalSection
|
||||
|
||||
@@ -10,7 +10,7 @@ using mRemoteNG.UI.Window;
|
||||
namespace mRemoteNG.App
|
||||
{
|
||||
[SupportedOSPlatform("windows")]
|
||||
public static class Windows
|
||||
public static class AppWindows
|
||||
{
|
||||
private static ActiveDirectoryImportWindow _adimportForm;
|
||||
private static ExternalToolsWindow _externalappsForm;
|
||||
@@ -26,8 +26,9 @@ namespace mRemoteNG.App
|
||||
|
||||
internal static ConfigWindow ConfigForm { get; set; } = new ConfigWindow();
|
||||
internal static ErrorAndInfoWindow ErrorsForm { get; set; } = new ErrorAndInfoWindow();
|
||||
private static UpdateWindow UpdateForm { get; set; } = new UpdateWindow();
|
||||
internal static UpdateWindow UpdateForm { get; set; } = new UpdateWindow();
|
||||
internal static SSHTransferWindow SshtransferForm { get; private set; } = new SSHTransferWindow();
|
||||
internal static OptionsWindow OptionsFormWindow { get; private set; }
|
||||
|
||||
|
||||
public static void Show(WindowType windowType)
|
||||
@@ -44,8 +45,10 @@ namespace mRemoteNG.App
|
||||
_adimportForm.Show(dockPanel);
|
||||
break;
|
||||
case WindowType.Options:
|
||||
FrmMain.OptionsForm.SetActivatedPage(Language.StartupExit);
|
||||
FrmMain.OptionsForm.Visible = true;
|
||||
if (OptionsFormWindow == null || OptionsFormWindow.IsDisposed)
|
||||
OptionsFormWindow = new OptionsWindow();
|
||||
OptionsFormWindow.SetActivatedPage(Language.StartupExit);
|
||||
OptionsFormWindow.Show(dockPanel);
|
||||
break;
|
||||
case WindowType.SSHTransfer:
|
||||
if (SshtransferForm == null || SshtransferForm.IsDisposed)
|
||||
124
mRemoteNG/App/Checks/DotNetRuntimeCheck.cs
Normal file
124
mRemoteNG/App/Checks/DotNetRuntimeCheck.cs
Normal file
@@ -0,0 +1,124 @@
|
||||
using Microsoft.Win32;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Runtime.Versioning;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Forms;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace mRemoteNG.DotNet.Update
|
||||
{
|
||||
[SupportedOSPlatform("windows")]
|
||||
public class DotNetRuntimeCheck
|
||||
{
|
||||
public const string RequiredDotnetVersion = "9.0";
|
||||
private const string ReleaseFeedUrl = "https://dotnetcli.blob.core.windows.net/dotnet/release-metadata/releases-index.json";
|
||||
|
||||
#region Installed Version Check
|
||||
/// <summary>
|
||||
/// Gets the installed .NET 9 runtime version if present
|
||||
/// </summary>
|
||||
/// <returns>The version string (e.g., "v9.0.0") or null if not found</returns>
|
||||
[SupportedOSPlatform("windows")]
|
||||
public static string? GetLatestDotNetRuntimeVersion()
|
||||
{
|
||||
string[] registryPaths = new[]
|
||||
{
|
||||
@"SOFTWARE\dotnet\Setup\InstalledVersions\x86",
|
||||
@"SOFTWARE\dotnet\Setup\InstalledVersions\x64",
|
||||
@"SOFTWARE\dotnet\Setup\InstalledVersions\arm64"
|
||||
};
|
||||
|
||||
foreach (string path in registryPaths)
|
||||
{
|
||||
try
|
||||
{
|
||||
using RegistryKey? key = Registry.LocalMachine.OpenSubKey(path);
|
||||
if (key == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check for the "sharedhost" subkey
|
||||
using (RegistryKey? sharedHostKey = key.OpenSubKey("sharedhost"))
|
||||
{
|
||||
if (sharedHostKey == null) {
|
||||
continue;
|
||||
};
|
||||
|
||||
// Look for the "Version" value in sharedhost
|
||||
object? versionValue = sharedHostKey.GetValue("Version");
|
||||
if (versionValue != null)
|
||||
{
|
||||
string? version = versionValue.ToString();
|
||||
if (!string.IsNullOrWhiteSpace(version))
|
||||
{
|
||||
return version;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"Error checking registry fallback: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
#endregion Installed Version Check
|
||||
#region Latest Online Version Check
|
||||
public static async Task<(string latestRuntimeVersion, string downloadUrl)> GetLatestAvailableDotNetVersionAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
using var httpClient = new HttpClient();
|
||||
httpClient.DefaultRequestHeaders.Add("User-Agent", "DotNetRuntimeChecker");
|
||||
|
||||
string jsonContent = await httpClient.GetStringAsync(ReleaseFeedUrl);
|
||||
JObject releasesIndex = JObject.Parse(jsonContent);
|
||||
|
||||
// Find the entry for .NET matching RequiredDotnetVersion
|
||||
JToken? dotnetEntry = releasesIndex["releases-index"]?.FirstOrDefault(entry => entry["channel-version"]?.ToString() == RequiredDotnetVersion);
|
||||
|
||||
if (dotnetEntry != null && dotnetEntry["latest-runtime"] != null)
|
||||
{
|
||||
string? latestRuntimeVersion = dotnetEntry["latest-runtime"]?.ToString();
|
||||
string arch;
|
||||
switch (RuntimeInformation.OSArchitecture)
|
||||
{
|
||||
case Architecture.Arm64:
|
||||
arch = "arm64";
|
||||
break;
|
||||
case Architecture.X86:
|
||||
arch = "x86";
|
||||
break;
|
||||
case Architecture.X64:
|
||||
arch = "x64";
|
||||
break;
|
||||
default:
|
||||
throw new NotSupportedException($"Unsupported architecture: {RuntimeInformation.OSArchitecture}");
|
||||
}
|
||||
if (!string.IsNullOrEmpty(latestRuntimeVersion))
|
||||
{
|
||||
// Construct the download URL using the latest version
|
||||
string downloadUrl = $"https://dotnet.microsoft.com/en-us/download/dotnet/thank-you/runtime-desktop-{latestRuntimeVersion}-windows-{arch}-installer";
|
||||
return (latestRuntimeVersion, downloadUrl);
|
||||
}
|
||||
}
|
||||
|
||||
return ("Unknown", "");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"Error fetching latest version: {ex.Message}");
|
||||
return ("Unknown", "");
|
||||
}
|
||||
}
|
||||
#endregion Latest Online Version Check
|
||||
}
|
||||
}
|
||||
26
mRemoteNG/App/Checks/InternetConnection.cs
Normal file
26
mRemoteNG/App/Checks/InternetConnection.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace mRemoteNG.App.Update
|
||||
{
|
||||
public class InternetConnection
|
||||
{
|
||||
public static bool IsPosible()
|
||||
{
|
||||
try
|
||||
{
|
||||
using var client = new HttpClient();
|
||||
client.Timeout = TimeSpan.FromSeconds(5);
|
||||
return client.GetAsync("https://www.microsoft.com").Result.IsSuccessStatusCode;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
42
mRemoteNG/App/Checks/VCppRuntimeCheck.cs
Normal file
42
mRemoteNG/App/Checks/VCppRuntimeCheck.cs
Normal file
@@ -0,0 +1,42 @@
|
||||
using Microsoft.Win32;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.Versioning; // Add for SupportedOSPlatform
|
||||
|
||||
namespace mRemoteNG.App.Update
|
||||
{
|
||||
public class VCppRuntimeCheck
|
||||
{
|
||||
[SupportedOSPlatform("windows")]
|
||||
public static List<string> GetInstalledVcRedistVersions()
|
||||
{
|
||||
var installedVersions = new List<string>();
|
||||
var baseKeys = new[]
|
||||
{
|
||||
@"SOFTWARE\Microsoft\VisualStudio",
|
||||
@"SOFTWARE\WOW6432Node\Microsoft\VisualStudio"
|
||||
};
|
||||
|
||||
for (int major = 14; major <= 17; major++) // Covers 2015–2022+
|
||||
{
|
||||
for (int minor = 0; minor <= 3; minor++)
|
||||
{
|
||||
string version = $"{major}.{minor}";
|
||||
foreach (var baseKey in baseKeys)
|
||||
{
|
||||
string path = $@"{baseKey}\{version}\VC\Runtimes\x64";
|
||||
using (RegistryKey? key = Registry.LocalMachine.OpenSubKey(path))
|
||||
{
|
||||
if (key?.GetValue("Installed") is int installed && installed == 1)
|
||||
{
|
||||
installedVersions.Add(version);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return installedVersions;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,8 +15,10 @@ namespace mRemoteNG.App.Info
|
||||
{
|
||||
public const string UrlHome = "https://mremoteng.org";
|
||||
public const string UrlDonate = "https://mremoteng.org/contribute";
|
||||
public const string UrlForum = "https://www.reddit.com/r/mRemoteNG";
|
||||
public const string UrlBugs = "https://bugs.mremoteng.org";
|
||||
public const string UrlForum = "https://github.com/orgs/mRemoteNG/discussions";
|
||||
public const string UrlChat = "https://app.element.io/#/room/#mremoteng:matrix.org";
|
||||
public const string UrlCommunity = "https://www.reddit.com/r/mRemoteNG";
|
||||
public const string UrlBugs = "https://github.com/mRemoteNG/mRemoteNG/issues/new";
|
||||
public const string UrlDocumentation = "https://mremoteng.readthedocs.io/en/latest/";
|
||||
public static readonly string ApplicationVersion = Application.ProductVersion;
|
||||
public static readonly string ProductName = Application.ProductName;
|
||||
@@ -54,8 +56,11 @@ namespace mRemoteNG.App.Info
|
||||
|
||||
public static Version GetApplicationVersion()
|
||||
{
|
||||
_ = System.Version.TryParse(ApplicationVersion, out Version v);
|
||||
return v;
|
||||
string cleanedVersion = ApplicationVersion.Split(' ')[0].Replace("(", "").Replace(")", "").Replace("Build", "");
|
||||
cleanedVersion = cleanedVersion + "." + ApplicationVersion.Split(' ')[^1].Replace(")", "");
|
||||
|
||||
_ = System.Version.TryParse(cleanedVersion, out Version parsedVersion);
|
||||
return parsedVersion;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -57,9 +57,9 @@ namespace mRemoteNG.App.Initialization
|
||||
return new OnlyLogMessageFilter(
|
||||
new MessageTypeFilterDecorator(
|
||||
new NotificationPanelMessageFilteringOptions(),
|
||||
new MessageFocusDecorator(Windows.ErrorsForm,
|
||||
new MessageFocusDecorator(AppWindows.ErrorsForm,
|
||||
new NotificationPanelSwitchOnMessageFilteringOptions(),
|
||||
new NotificationPanelMessageWriter(Windows.ErrorsForm))
|
||||
new NotificationPanelMessageWriter(AppWindows.ErrorsForm))
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -9,14 +9,9 @@ using mRemoteNG.Resources.Language;
|
||||
namespace mRemoteNG.App.Initialization
|
||||
{
|
||||
[SupportedOSPlatform("windows")]
|
||||
public class StartupDataLogger
|
||||
public class StartupDataLogger(MessageCollector messageCollector)
|
||||
{
|
||||
private readonly MessageCollector _messageCollector;
|
||||
|
||||
public StartupDataLogger(MessageCollector messageCollector)
|
||||
{
|
||||
_messageCollector = messageCollector ?? throw new ArgumentNullException(nameof(messageCollector));
|
||||
}
|
||||
private readonly MessageCollector _messageCollector = messageCollector ?? throw new ArgumentNullException(nameof(messageCollector));
|
||||
|
||||
public void LogStartupData()
|
||||
{
|
||||
|
||||
@@ -1,101 +1,158 @@
|
||||
using System;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
|
||||
using mRemoteNG.App.Update;
|
||||
using mRemoteNG.Config.Settings;
|
||||
using mRemoteNG.DotNet.Update;
|
||||
using mRemoteNG.UI.Forms;
|
||||
using mRemoteNG.Resources.Language;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.Versioning;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Forms;
|
||||
using mRemoteNG.Config.Settings;
|
||||
using mRemoteNG.UI.Forms;
|
||||
|
||||
|
||||
|
||||
namespace mRemoteNG.App
|
||||
{
|
||||
[SupportedOSPlatform("windows")]
|
||||
public static class ProgramRoot
|
||||
{
|
||||
private static Mutex _mutex;
|
||||
private static FrmSplashScreenNew _frmSplashScreen = null;
|
||||
private static Mutex? _mutex;
|
||||
private static string customResourcePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Languages");
|
||||
|
||||
/// <summary>
|
||||
/// The main entry point for the application.
|
||||
/// </summary>
|
||||
private static System.Threading.Thread? _wpfSplashThread;
|
||||
private static FrmSplashScreenNew? _wpfSplash;
|
||||
|
||||
[STAThread]
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
Trace.WriteLine("!!!!!!=============== TEST ==================!!!!!!!!!!!!!");
|
||||
// Forcing to load System.Configuration.ConfigurationManager before any other assembly to be able to check settings
|
||||
try
|
||||
{
|
||||
string assemblyFile = "System.Configuration.ConfigurationManager" + ".dll";
|
||||
string assemblyPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Assemblies", assemblyFile);
|
||||
// Ensure the real entry point is definitely STA
|
||||
MainAsync(args).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
|
||||
if (File.Exists(assemblyPath))
|
||||
{
|
||||
Assembly.LoadFrom(assemblyPath);
|
||||
}
|
||||
}
|
||||
catch (FileNotFoundException ex)
|
||||
{
|
||||
Trace.WriteLine("Error occured: " + ex.Message);
|
||||
}
|
||||
|
||||
//Subscribe to AssemblyResolve event
|
||||
private static Task MainAsync(string[] args)
|
||||
{
|
||||
AppDomain.CurrentDomain.AssemblyResolve += OnAssemblyResolve;
|
||||
|
||||
//Check if local settings DB exist or accessible
|
||||
CheckLockalDB();
|
||||
#if !SELF_CONTAINED
|
||||
// Runtime checks only needed for framework-dependent deployments
|
||||
// Self-contained builds include the runtime, so no check is needed
|
||||
string? installedVersion = DotNetRuntimeCheck.GetLatestDotNetRuntimeVersion();
|
||||
//installedVersion = ""; // Force check for testing purposes
|
||||
|
||||
Lazy<bool> singleInstanceOption = new Lazy<bool>(() => Properties.OptionsStartupExitPage.Default.SingleInstance);
|
||||
var checkFail = false;
|
||||
|
||||
// Checking .NET Runtime version
|
||||
var (latestRuntimeVersion, downloadUrl) = DotNetRuntimeCheck.GetLatestAvailableDotNetVersionAsync().GetAwaiter().GetResult();
|
||||
if (string.IsNullOrEmpty(installedVersion))
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = ShowDownloadCancelDialog(
|
||||
$".NET " + DotNetRuntimeCheck.RequiredDotnetVersion + ".0 " + Language.MsgRuntimeIsRequired + "\n\n" +
|
||||
Language.MsgDownloadLatestRuntime + "\n" + downloadUrl + "\n\n" +
|
||||
Language.MsgExit + "\n\n",
|
||||
Language.MsgMissingRuntime + " .NET " + DotNetRuntimeCheck.RequiredDotnetVersion);
|
||||
|
||||
if (result == DialogResult.OK && InternetConnection.IsPosible())
|
||||
{
|
||||
try
|
||||
{
|
||||
Process.Start(new ProcessStartInfo(fileName: downloadUrl) { UseShellExecute = true });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
MessageBox.Show($"Unable to open download link: {ex.Message}", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
checkFail = true;
|
||||
}
|
||||
|
||||
// Checking Visual C++ Redistributable version
|
||||
if (VCppRuntimeCheck.GetInstalledVcRedistVersions() == null || VCppRuntimeCheck.GetInstalledVcRedistVersions().Count == 0)
|
||||
{
|
||||
var downloadUrl2 = "https://aka.ms/vs/17/release/vc_redist.x64.exe";
|
||||
try
|
||||
{
|
||||
var result = ShowDownloadCancelDialog(
|
||||
$"A Visual C++ (MSVC) " + Language.MsgRuntimeIsRequired + "\n\n" +
|
||||
Language.MsgDownloadLatestRuntime + "\n" + downloadUrl2 + "\n\n" +
|
||||
Language.MsgExit + "\n\n",
|
||||
Language.MsgMissingRuntime + " Visual C++ Redistributable x64");
|
||||
|
||||
if (result == DialogResult.OK && InternetConnection.IsPosible())
|
||||
{
|
||||
try
|
||||
{
|
||||
Process.Start(new ProcessStartInfo(fileName: downloadUrl2) { UseShellExecute = true });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
MessageBox.Show($"Unable to open download link: {ex.Message}", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
checkFail = true;
|
||||
}
|
||||
|
||||
if (checkFail)
|
||||
{
|
||||
Environment.Exit(0);
|
||||
}
|
||||
#endif
|
||||
|
||||
Lazy<bool> singleInstanceOption = new(() => Properties.OptionsStartupExitPage.Default.SingleInstance);
|
||||
if (singleInstanceOption.Value)
|
||||
{
|
||||
StartApplicationAsSingleInstance();
|
||||
}
|
||||
else
|
||||
{
|
||||
StartApplication();
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
// Assembly resolve handler
|
||||
private static Assembly? OnAssemblyResolve(object? sender, ResolveEventArgs args)
|
||||
{
|
||||
try
|
||||
{
|
||||
string assemblyName = new AssemblyName(args.Name).Name ?? string.Empty;
|
||||
if (assemblyName.EndsWith(".resources", StringComparison.OrdinalIgnoreCase))
|
||||
return null;
|
||||
|
||||
string assemblyFile = assemblyName + ".dll";
|
||||
string assemblyPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Assemblies", assemblyFile);
|
||||
|
||||
if (File.Exists(assemblyPath))
|
||||
return Assembly.LoadFrom(assemblyPath);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Suppress resolution exceptions; return null to continue standard probing
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static void CheckLockalDB()
|
||||
{
|
||||
LocalSettingsDBManager settingsManager = new LocalSettingsDBManager(dbPath: "mRemoteNG.appSettings", useEncryption: false, schemaFilePath: "");
|
||||
LocalDBManager settingsManager = new LocalDBManager(dbPath: "mRemoteNG.appSettings", useEncryption: false, schemaFilePath: "");
|
||||
}
|
||||
private static Assembly OnAssemblyResolve(object sender, ResolveEventArgs resolveArgs)
|
||||
{
|
||||
string assemblyName = new AssemblyName(resolveArgs.Name).Name.Replace(".resources", string.Empty);
|
||||
string assemblyFile = assemblyName + ".dll";
|
||||
string assemblyPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Assemblies", assemblyFile);
|
||||
|
||||
|
||||
if (File.Exists(assemblyPath))
|
||||
{
|
||||
return Assembly.LoadFrom(assemblyPath);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static void StartApplication()
|
||||
{
|
||||
CatchAllUnhandledExceptions();
|
||||
Application.EnableVisualStyles();
|
||||
Application.SetCompatibleTextRenderingDefault(false);
|
||||
|
||||
_frmSplashScreen = FrmSplashScreenNew.GetInstance();
|
||||
|
||||
Screen targetScreen = Screen.PrimaryScreen;
|
||||
|
||||
Rectangle viewport = targetScreen.WorkingArea;
|
||||
_frmSplashScreen.Top = viewport.Top;
|
||||
_frmSplashScreen.Left = viewport.Left;
|
||||
// normally it should be screens[1] however due DPI apply 1 size "same" as default with 100%
|
||||
_frmSplashScreen.Left = viewport.Left + (targetScreen.Bounds.Size.Width - _frmSplashScreen.Width) / 2;
|
||||
_frmSplashScreen.Top = viewport.Top + (targetScreen.Bounds.Size.Height - _frmSplashScreen.Height) / 2;
|
||||
_frmSplashScreen.ShowInTaskbar = false;
|
||||
_frmSplashScreen.Show();
|
||||
ShowSplashOnStaThread();
|
||||
|
||||
Application.Run(FrmMain.Default);
|
||||
}
|
||||
@@ -134,8 +191,24 @@ namespace mRemoteNG.App
|
||||
Process currentProcess = Process.GetCurrentProcess();
|
||||
foreach (Process enumeratedProcess in Process.GetProcessesByName(currentProcess.ProcessName))
|
||||
{
|
||||
// Safely check for null MainModule and FileName
|
||||
string? enumeratedFileName = null;
|
||||
string? currentFileName = null;
|
||||
try
|
||||
{
|
||||
enumeratedFileName = enumeratedProcess.MainModule?.FileName;
|
||||
currentFileName = currentProcess.MainModule?.FileName;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Access to MainModule can throw exceptions for some processes; ignore and continue
|
||||
continue;
|
||||
}
|
||||
|
||||
if (enumeratedProcess.Id != currentProcess.Id &&
|
||||
enumeratedProcess.MainModule.FileName == currentProcess.MainModule.FileName &&
|
||||
!string.IsNullOrEmpty(enumeratedFileName) &&
|
||||
!string.IsNullOrEmpty(currentFileName) &&
|
||||
enumeratedFileName == currentFileName &&
|
||||
enumeratedProcess.MainWindowHandle != IntPtr.Zero)
|
||||
windowHandle = enumeratedProcess.MainWindowHandle;
|
||||
}
|
||||
@@ -145,31 +218,152 @@ namespace mRemoteNG.App
|
||||
|
||||
private static void CatchAllUnhandledExceptions()
|
||||
{
|
||||
System.Windows.Forms.Application.ThreadException += ApplicationOnThreadException;
|
||||
System.Windows.Forms.Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException);
|
||||
Application.ThreadException += ApplicationOnThreadException;
|
||||
Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException);
|
||||
AppDomain.CurrentDomain.UnhandledException += CurrentDomainOnUnhandledException;
|
||||
}
|
||||
|
||||
private static void ApplicationOnThreadException(object sender, ThreadExceptionEventArgs e)
|
||||
{
|
||||
// if (PresentationSource.FromVisual(FrmSplashScreenNew))
|
||||
FrmSplashScreenNew.GetInstance().Close();
|
||||
|
||||
CloseSplash();
|
||||
if (FrmMain.Default.IsDisposed) return;
|
||||
|
||||
FrmUnhandledException window = new(e.Exception, false);
|
||||
window.ShowDialog(FrmMain.Default);
|
||||
}
|
||||
|
||||
private static void CurrentDomainOnUnhandledException(object sender, UnhandledExceptionEventArgs e)
|
||||
{
|
||||
//TODO: Check if splash closed properly
|
||||
//if (!FrmSplashScreenNew.GetInstance().IsDisposed)
|
||||
// FrmSplashScreenNew.GetInstance().Close();
|
||||
|
||||
FrmUnhandledException window = new(e.ExceptionObject as Exception, e.IsTerminating);
|
||||
window.ShowDialog(FrmMain.Default);
|
||||
}
|
||||
|
||||
|
||||
private static void ShowSplashOnStaThread()
|
||||
{
|
||||
_wpfSplashThread = new System.Threading.Thread(() =>
|
||||
{
|
||||
_wpfSplash = FrmSplashScreenNew.GetInstance();
|
||||
|
||||
// Center the splash screen on the primary screen before showing it
|
||||
_wpfSplash.WindowStartupLocation = System.Windows.WindowStartupLocation.CenterScreen;
|
||||
|
||||
_wpfSplash.ShowInTaskbar = false;
|
||||
_wpfSplash.Show();
|
||||
System.Windows.Forms.Integration.ElementHost.EnableModelessKeyboardInterop(_wpfSplash);
|
||||
System.Windows.Threading.Dispatcher.Run(); // WPF message loop
|
||||
})
|
||||
{ IsBackground = true };
|
||||
_wpfSplashThread.SetApartmentState(System.Threading.ApartmentState.STA);
|
||||
_wpfSplashThread.Start();
|
||||
}
|
||||
|
||||
private static void CloseSplash()
|
||||
{
|
||||
if (_wpfSplash != null)
|
||||
{
|
||||
_wpfSplash.Dispatcher.Invoke(() => _wpfSplash.Close());
|
||||
_wpfSplash = null;
|
||||
}
|
||||
if (_wpfSplashThread != null)
|
||||
{
|
||||
_wpfSplashThread.Join();
|
||||
_wpfSplashThread = null;
|
||||
}
|
||||
}
|
||||
|
||||
// Helper to show a dialog with "Download" and "Cancel" buttons.
|
||||
// Returns DialogResult.OK if Download clicked, otherwise DialogResult.Cancel.
|
||||
private static DialogResult ShowDownloadCancelDialog(string message, string caption)
|
||||
{
|
||||
using Form dialog = new Form()
|
||||
{
|
||||
Text = caption,
|
||||
StartPosition = FormStartPosition.CenterScreen,
|
||||
FormBorderStyle = FormBorderStyle.FixedDialog,
|
||||
MinimizeBox = false,
|
||||
MaximizeBox = false,
|
||||
ShowInTaskbar = false,
|
||||
ClientSize = new Size(560, 200),
|
||||
Icon = SystemIcons.Information
|
||||
};
|
||||
|
||||
// Try to find a URL in the message (very simple heuristic: first "http" until whitespace/newline)
|
||||
int urlStart = message.IndexOf("http", StringComparison.OrdinalIgnoreCase);
|
||||
string? url = null;
|
||||
if (urlStart >= 0)
|
||||
{
|
||||
int urlEnd = message.IndexOfAny(new char[] { ' ', '\r', '\n', '\t' }, urlStart);
|
||||
if (urlEnd == -1) urlEnd = message.Length;
|
||||
url = message.Substring(urlStart, urlEnd - urlStart);
|
||||
}
|
||||
|
||||
LinkLabel lbl = new LinkLabel()
|
||||
{
|
||||
AutoSize = false,
|
||||
Text = message,
|
||||
Location = new Point(12, 12),
|
||||
Size = new Size(dialog.ClientSize.Width - 24, dialog.ClientSize.Height - 60),
|
||||
TextAlign = ContentAlignment.TopLeft,
|
||||
LinkBehavior = LinkBehavior.SystemDefault
|
||||
};
|
||||
lbl.MaximumSize = new Size(dialog.ClientSize.Width - 24, 0);
|
||||
|
||||
if (!string.IsNullOrEmpty(url) && urlStart >= 0)
|
||||
{
|
||||
// Ensure link indices are within bounds of the LinkLabel text
|
||||
int linkStartInLabel = urlStart;
|
||||
int linkLength = url.Length;
|
||||
if (linkStartInLabel + linkLength <= lbl.Text.Length)
|
||||
{
|
||||
lbl.Links.Add(linkStartInLabel, linkLength, url);
|
||||
}
|
||||
}
|
||||
|
||||
lbl.LinkClicked += (s, e) =>
|
||||
{
|
||||
string? linkUrl = e.Link.LinkData as string;
|
||||
if (string.IsNullOrEmpty(linkUrl))
|
||||
return;
|
||||
if (!InternetConnection.IsPosible())
|
||||
{
|
||||
MessageBox.Show("No internet connection is available.", "Network", MessageBoxButtons.OK, MessageBoxIcon.Warning);
|
||||
return;
|
||||
}
|
||||
|
||||
// Treat clicking the link the same as clicking the "Download" button:
|
||||
// set DialogResult to OK so the caller receives DialogResult.OK and can proceed to open the download URL.
|
||||
dialog.DialogResult = DialogResult.OK;
|
||||
// Do not call Process.Start here to avoid duplicate launches; caller already opens the URL when it sees DialogResult.OK.
|
||||
};
|
||||
|
||||
Button btnDownload = new Button()
|
||||
{
|
||||
Text = "Download",
|
||||
DialogResult = DialogResult.OK,
|
||||
Size = new Size(100, 28),
|
||||
};
|
||||
Button btnCancel = new Button()
|
||||
{
|
||||
Text = "Cancel",
|
||||
DialogResult = DialogResult.Cancel,
|
||||
Size = new Size(100, 28),
|
||||
};
|
||||
|
||||
// Position buttons
|
||||
int padding = 12;
|
||||
btnCancel.Location = new Point(dialog.ClientSize.Width - padding - btnCancel.Width, dialog.ClientSize.Height - padding - btnCancel.Height);
|
||||
btnDownload.Location = new Point(btnCancel.Left - 8 - btnDownload.Width, btnCancel.Top);
|
||||
|
||||
// Set dialog defaults
|
||||
dialog.Controls.Add(lbl);
|
||||
dialog.Controls.Add(btnDownload);
|
||||
dialog.Controls.Add(btnCancel);
|
||||
dialog.AcceptButton = btnDownload;
|
||||
dialog.CancelButton = btnCancel;
|
||||
|
||||
// Adjust label height to wrap text properly
|
||||
lbl.Height = btnCancel.Top - lbl.Top - 8;
|
||||
|
||||
return dialog.ShowDialog();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -121,7 +121,11 @@ namespace mRemoteNG.App
|
||||
private static void RunUpdateFile()
|
||||
{
|
||||
if (UpdatePending)
|
||||
{
|
||||
// Validate the update file path to prevent command injection
|
||||
Tools.PathValidator.ValidateExecutablePathOrThrow(_updateFilePath, nameof(_updateFilePath));
|
||||
Process.Start(new ProcessStartInfo(_updateFilePath) { UseShellExecute = true });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -84,10 +84,8 @@ namespace mRemoteNG.App
|
||||
try
|
||||
{
|
||||
await _appUpdate.GetUpdateInfoAsync();
|
||||
if (_appUpdate.IsUpdateAvailable())
|
||||
{
|
||||
Windows.Show(WindowType.Update);
|
||||
}
|
||||
// Update is available, but don't show the panel automatically at startup
|
||||
// User can check for updates manually via Help > Check for Updates menu
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
@@ -19,22 +19,16 @@ using mRemoteNG.Tree.Root;
|
||||
namespace mRemoteNG.Config.Connections
|
||||
{
|
||||
[SupportedOSPlatform("windows")]
|
||||
public class SqlConnectionsLoader : IConnectionsLoader
|
||||
public class SqlConnectionsLoader(
|
||||
IDeserializer<string, IEnumerable<LocalConnectionPropertiesModel>> localConnectionPropertiesDeserializer,
|
||||
IDataProvider<string> dataProvider) : IConnectionsLoader
|
||||
{
|
||||
private readonly IDeserializer<string, IEnumerable<LocalConnectionPropertiesModel>> _localConnectionPropertiesDeserializer;
|
||||
private readonly IDeserializer<string, IEnumerable<LocalConnectionPropertiesModel>> _localConnectionPropertiesDeserializer = localConnectionPropertiesDeserializer.ThrowIfNull(nameof(localConnectionPropertiesDeserializer));
|
||||
|
||||
private readonly IDataProvider<string> _dataProvider;
|
||||
private readonly IDataProvider<string> _dataProvider = dataProvider.ThrowIfNull(nameof(dataProvider));
|
||||
|
||||
private Func<Optional<SecureString>> AuthenticationRequestor { get; set; } = () => MiscTools.PasswordDialog("", false);
|
||||
|
||||
public SqlConnectionsLoader(
|
||||
IDeserializer<string, IEnumerable<LocalConnectionPropertiesModel>> localConnectionPropertiesDeserializer,
|
||||
IDataProvider<string> dataProvider)
|
||||
{
|
||||
_localConnectionPropertiesDeserializer = localConnectionPropertiesDeserializer.ThrowIfNull(nameof(localConnectionPropertiesDeserializer));
|
||||
_dataProvider = dataProvider.ThrowIfNull(nameof(dataProvider));
|
||||
}
|
||||
|
||||
public ConnectionTreeModel Load()
|
||||
{
|
||||
IDatabaseConnector connector = DatabaseConnectorFactory.DatabaseConnectorFromSettings();
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Linq;
|
||||
using System.Data.Common;
|
||||
using mRemoteNG.App;
|
||||
using mRemoteNG.App.Info;
|
||||
using mRemoteNG.Config.DatabaseConnectors;
|
||||
@@ -23,18 +24,11 @@ using mRemoteNG.Config.Serializers.ConnectionSerializers.Sql;
|
||||
namespace mRemoteNG.Config.Connections
|
||||
{
|
||||
[SupportedOSPlatform("windows")]
|
||||
public class SqlConnectionsSaver : ISaver<ConnectionTreeModel>
|
||||
public class SqlConnectionsSaver(SaveFilter saveFilter, ISerializer<IEnumerable<LocalConnectionPropertiesModel>, string> localPropertieSerializer, IDataProvider<string> localPropertiesDataProvider) : ISaver<ConnectionTreeModel>
|
||||
{
|
||||
private readonly SaveFilter _saveFilter;
|
||||
private readonly ISerializer<IEnumerable<LocalConnectionPropertiesModel>, string> _localPropertiesSerializer;
|
||||
private readonly IDataProvider<string> _dataProvider;
|
||||
|
||||
public SqlConnectionsSaver(SaveFilter saveFilter, ISerializer<IEnumerable<LocalConnectionPropertiesModel>, string> localPropertieSerializer, IDataProvider<string> localPropertiesDataProvider)
|
||||
{
|
||||
_saveFilter = saveFilter ?? throw new ArgumentNullException(nameof(saveFilter));
|
||||
_localPropertiesSerializer = localPropertieSerializer.ThrowIfNull(nameof(localPropertieSerializer));
|
||||
_dataProvider = localPropertiesDataProvider.ThrowIfNull(nameof(localPropertiesDataProvider));
|
||||
}
|
||||
private readonly SaveFilter _saveFilter = saveFilter ?? throw new ArgumentNullException(nameof(saveFilter));
|
||||
private readonly ISerializer<IEnumerable<LocalConnectionPropertiesModel>, string> _localPropertiesSerializer = localPropertieSerializer.ThrowIfNull(nameof(localPropertieSerializer));
|
||||
private readonly IDataProvider<string> _dataProvider = localPropertiesDataProvider.ThrowIfNull(nameof(localPropertiesDataProvider));
|
||||
|
||||
public void Save(ConnectionTreeModel connectionTreeModel, string propertyNameTrigger = "")
|
||||
{
|
||||
@@ -133,11 +127,20 @@ namespace mRemoteNG.Config.Connections
|
||||
|
||||
if (rootTreeNode != null)
|
||||
{
|
||||
dbQuery =
|
||||
databaseConnector.DbCommand(
|
||||
"INSERT INTO tblRoot (Name, Export, Protected, ConfVersion) VALUES('" +
|
||||
MiscTools.PrepareValueForDB(rootTreeNode.Name) + "', 0, '" + strProtected + "','" +
|
||||
ConnectionsFileInfo.ConnectionFileVersion + "')");
|
||||
dbQuery = databaseConnector.DbCommand(
|
||||
"INSERT INTO tblRoot (Name, Export, Protected, ConfVersion) VALUES(@Name, 0, @Protected, @Version)");
|
||||
DbParameter nameParam = dbQuery.CreateParameter();
|
||||
nameParam.ParameterName = "@Name";
|
||||
nameParam.Value = rootTreeNode.Name;
|
||||
DbParameter protectedParam = dbQuery.CreateParameter();
|
||||
protectedParam.ParameterName = "@Protected";
|
||||
protectedParam.Value = strProtected;
|
||||
DbParameter versionParam = dbQuery.CreateParameter();
|
||||
versionParam.ParameterName = "@Version";
|
||||
versionParam.Value = ConnectionsFileInfo.ConnectionFileVersion;
|
||||
dbQuery.Parameters.Add(nameParam);
|
||||
dbQuery.Parameters.Add(protectedParam);
|
||||
dbQuery.Parameters.Add(versionParam);
|
||||
dbQuery.ExecuteNonQuery();
|
||||
}
|
||||
else
|
||||
@@ -165,7 +168,15 @@ namespace mRemoteNG.Config.Connections
|
||||
// TODO: use transaction
|
||||
System.Data.Common.DbCommand dbQuery = databaseConnector.DbCommand("TRUNCATE TABLE tblUpdate");
|
||||
dbQuery.ExecuteNonQuery();
|
||||
dbQuery = databaseConnector.DbCommand("INSERT INTO tblUpdate (LastUpdate) VALUES('" + MiscTools.DBDate(DateTime.Now.ToUniversalTime()) + "')");
|
||||
dbQuery = databaseConnector.DbCommand("INSERT INTO tblUpdate (LastUpdate) VALUES(@LastUpdate)");
|
||||
|
||||
DbParameter lastUpdateParam = dbQuery.CreateParameter();
|
||||
lastUpdateParam.ParameterName = "@LastUpdate";
|
||||
// Use DBTimeStampNow() instead of DBDate() - the column is datetime type, not string
|
||||
// DBTimeStampNow() returns the database-specific .NET type: DateTime for MSSQL, MySqlDateTime for MySQL
|
||||
lastUpdateParam.Value = MiscTools.DBTimeStampNow();
|
||||
dbQuery.Parameters.Add(lastUpdateParam);
|
||||
|
||||
dbQuery.ExecuteNonQuery();
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ using System.Runtime.Versioning;
|
||||
using mRemoteNG.App;
|
||||
using mRemoteNG.Messages;
|
||||
using mRemoteNG.Resources.Language;
|
||||
using mRemoteNG.Tools;
|
||||
|
||||
namespace mRemoteNG.Config.DataProviders
|
||||
{
|
||||
@@ -17,8 +18,13 @@ namespace mRemoteNG.Config.DataProviders
|
||||
if (WeDontNeedToBackup(fileName))
|
||||
return;
|
||||
|
||||
PathValidator.ValidatePathOrThrow(fileName, nameof(fileName));
|
||||
|
||||
string backupFileName =
|
||||
string.Format(Properties.OptionsBackupPage.Default.BackupFileNameFormat, fileName, DateTime.Now);
|
||||
|
||||
PathValidator.ValidatePathOrThrow(backupFileName, nameof(backupFileName));
|
||||
|
||||
File.Copy(fileName, backupFileName);
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using mRemoteNG.Tools;
|
||||
|
||||
namespace mRemoteNG.Config.DataProviders
|
||||
{
|
||||
@@ -7,6 +8,8 @@ namespace mRemoteNG.Config.DataProviders
|
||||
{
|
||||
public void PruneBackupFiles(string filePath, int maxBackupsToKeep)
|
||||
{
|
||||
PathValidator.ValidatePathOrThrow(filePath, nameof(filePath));
|
||||
|
||||
string fileName = Path.GetFileName(filePath);
|
||||
string directoryName = Path.GetDirectoryName(filePath);
|
||||
|
||||
|
||||
@@ -2,18 +2,30 @@
|
||||
using System.IO;
|
||||
using System.Runtime.Versioning;
|
||||
using mRemoteNG.App;
|
||||
using mRemoteNG.Tools;
|
||||
|
||||
namespace mRemoteNG.Config.DataProviders
|
||||
{
|
||||
[SupportedOSPlatform("windows")]
|
||||
public class FileDataProvider : IDataProvider<string>
|
||||
{
|
||||
private string _filePath;
|
||||
|
||||
[SupportedOSPlatform("windows")]
|
||||
public string FilePath { get; set; }
|
||||
public string FilePath
|
||||
{
|
||||
get => _filePath;
|
||||
set
|
||||
{
|
||||
PathValidator.ValidatePathOrThrow(value, nameof(FilePath));
|
||||
_filePath = value;
|
||||
}
|
||||
}
|
||||
|
||||
public FileDataProvider(string filePath)
|
||||
{
|
||||
FilePath = filePath;
|
||||
PathValidator.ValidatePathOrThrow(filePath, nameof(filePath));
|
||||
_filePath = filePath;
|
||||
}
|
||||
|
||||
public virtual string Load()
|
||||
@@ -59,6 +71,7 @@ namespace mRemoteNG.Config.DataProviders
|
||||
{
|
||||
try
|
||||
{
|
||||
PathValidator.ValidatePathOrThrow(newPath, nameof(newPath));
|
||||
File.Move(FilePath, newPath);
|
||||
FilePath = newPath;
|
||||
}
|
||||
|
||||
@@ -3,14 +3,9 @@
|
||||
namespace mRemoteNG.Config.DataProviders
|
||||
{
|
||||
[SupportedOSPlatform("windows")]
|
||||
public class FileDataProviderWithRollingBackup : FileDataProvider
|
||||
public class FileDataProviderWithRollingBackup(string filePath) : FileDataProvider(filePath)
|
||||
{
|
||||
private readonly FileBackupCreator _fileBackupCreator;
|
||||
|
||||
public FileDataProviderWithRollingBackup(string filePath) : base(filePath)
|
||||
{
|
||||
_fileBackupCreator = new FileBackupCreator();
|
||||
}
|
||||
private readonly FileBackupCreator _fileBackupCreator = new FileBackupCreator();
|
||||
|
||||
public override void Save(string content)
|
||||
{
|
||||
|
||||
@@ -1,13 +1,8 @@
|
||||
namespace mRemoteNG.Config.DataProviders
|
||||
{
|
||||
public class InMemoryStringDataProvider : IDataProvider<string>
|
||||
public class InMemoryStringDataProvider(string initialContents = "") : IDataProvider<string>
|
||||
{
|
||||
private string _contents;
|
||||
|
||||
public InMemoryStringDataProvider(string initialContents = "")
|
||||
{
|
||||
_contents = initialContents;
|
||||
}
|
||||
private string _contents = initialContents;
|
||||
|
||||
public string Load()
|
||||
{
|
||||
|
||||
@@ -3,20 +3,15 @@ using mRemoteNG.Config.DatabaseConnectors;
|
||||
using mRemoteNG.Messages;
|
||||
using mRemoteNG.App;
|
||||
using MySql.Data.MySqlClient;
|
||||
using System.Data.SqlClient;
|
||||
using Microsoft.Data.SqlClient;
|
||||
using System.Runtime.Versioning;
|
||||
|
||||
namespace mRemoteNG.Config.DataProviders
|
||||
{
|
||||
[SupportedOSPlatform("windows")]
|
||||
public class SqlDataProvider : IDataProvider<DataTable>
|
||||
public class SqlDataProvider(IDatabaseConnector databaseConnector) : IDataProvider<DataTable>
|
||||
{
|
||||
public IDatabaseConnector DatabaseConnector { get; }
|
||||
|
||||
public SqlDataProvider(IDatabaseConnector databaseConnector)
|
||||
{
|
||||
DatabaseConnector = databaseConnector;
|
||||
}
|
||||
public IDatabaseConnector DatabaseConnector { get; } = databaseConnector;
|
||||
|
||||
public DataTable Load()
|
||||
{
|
||||
|
||||
@@ -1,44 +1,149 @@
|
||||
using System;
|
||||
using System.Data.SqlClient;
|
||||
using Microsoft.Data.SqlClient;
|
||||
using System.Runtime.Versioning;
|
||||
using System.Threading.Tasks;
|
||||
using System.Runtime.InteropServices;
|
||||
using LiteDB;
|
||||
using mRemoteNG.Resources.Language;
|
||||
|
||||
namespace mRemoteNG.Config.DatabaseConnectors
|
||||
{
|
||||
[SupportedOSPlatform("windows")]
|
||||
//[SupportedOSPlatform("windows")]
|
||||
/// <summary>
|
||||
/// A helper class for testing database connectivity
|
||||
/// </summary>
|
||||
///
|
||||
using System;
|
||||
using System.Data.SqlClient;
|
||||
|
||||
public class DatabaseConnectionTester
|
||||
{
|
||||
public async Task<ConnectionTestResult> TestConnectivity(string type,
|
||||
string server,
|
||||
string database,
|
||||
string username,
|
||||
string password)
|
||||
public async Task<ConnectionTestResult> TestConnectivity(string type, string server, string database, string username, string password)
|
||||
{
|
||||
using (IDatabaseConnector dbConnector = DatabaseConnectorFactory.DatabaseConnector(type, server, database, username, password))
|
||||
try
|
||||
{
|
||||
try
|
||||
// Build the connection string based on the provided parameters
|
||||
string connectionString = $"Data Source={server};Initial Catalog={database};User ID={username};Password={password}";
|
||||
|
||||
// Attempt to open a connection to the database
|
||||
using (SqlConnection connection = new SqlConnection(connectionString))
|
||||
{
|
||||
await dbConnector.ConnectAsync();
|
||||
return ConnectionTestResult.ConnectionSucceded;
|
||||
await connection.OpenAsync();
|
||||
}
|
||||
catch (SqlException sqlException)
|
||||
|
||||
return ConnectionTestResult.ConnectionSucceded;
|
||||
}
|
||||
catch (SqlException ex)
|
||||
{
|
||||
// Handle specific SQL exceptions
|
||||
switch (ex.Number)
|
||||
{
|
||||
if (sqlException.Message.Contains("The server was not found"))
|
||||
return ConnectionTestResult.ServerNotAccessible;
|
||||
if (sqlException.Message.Contains("Cannot open database"))
|
||||
case 4060: // Invalid Database
|
||||
return ConnectionTestResult.UnknownDatabase;
|
||||
if (sqlException.Message.Contains("Login failed for user"))
|
||||
case 18456: // Login Failed
|
||||
return ConnectionTestResult.CredentialsRejected;
|
||||
return ConnectionTestResult.UnknownError;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return ConnectionTestResult.UnknownError;
|
||||
case -1: // Server not accessible
|
||||
return ConnectionTestResult.ServerNotAccessible;
|
||||
default:
|
||||
return ConnectionTestResult.UnknownError;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Handle any other exceptions
|
||||
return ConnectionTestResult.UnknownError;
|
||||
}
|
||||
}
|
||||
}
|
||||
//public class DatabaseConnectionTester
|
||||
//{
|
||||
//public async Task<ConnectionTestResult> TestConnectivity(string type, string server, string database, string username, string password)
|
||||
//{
|
||||
//using IDatabaseConnector dbConnector = DatabaseConnectorFactory.DatabaseConnector(type, server, database, username, password);
|
||||
//try
|
||||
//{
|
||||
// Validate architecture compatibility
|
||||
//if (!Environment.Is64BitProcess)
|
||||
//{
|
||||
// throw new PlatformNotSupportedException("The application must run in a 64-bit process to use this database connector.");
|
||||
// }
|
||||
|
||||
// Attempt to connect
|
||||
|
||||
//using (SqlConnection connection = new SqlConnection("Data Source=172.22.155.100,1433;Initial Catalog=Demo;Integrated Security=False;User ID=sa;Password=London123;Multiple Active Result Sets=True;Connect Timeout=30;Encrypt=True;Trust Server Certificate=True;Application Name=mRemoteNG;Application Intent=ReadOnly"))
|
||||
//{
|
||||
// connection.Open();
|
||||
// Console.WriteLine("Connection successful!");
|
||||
//}
|
||||
//Console.WriteLine($"{RuntimeInformation.OSArchitecture}");
|
||||
//Console.WriteLine($"{RuntimeInformation.ProcessArchitecture}");
|
||||
//try
|
||||
//{
|
||||
// using (SqlConnection connection = new SqlConnection("Data Source=172.22.155.100,1433;Initial Catalog=Demo;Integrated Security=False;User ID=sa;Password=London123;Multiple Active Result Sets=True;Connect Timeout=30;Encrypt=True;Trust Server Certificate=True;Application Name=mRemoteNG;Application Intent=ReadOnly"))
|
||||
// {
|
||||
// connection.Open();
|
||||
// Console.WriteLine("Connection successful!");
|
||||
// }
|
||||
//}
|
||||
//catch (Exception ex)
|
||||
//{
|
||||
// Console.WriteLine($"Connection failed: {ex.Message}");
|
||||
//}
|
||||
//}
|
||||
/*
|
||||
|
||||
|
||||
try
|
||||
{
|
||||
using (SqlConnection connection = new SqlConnection("Data Source=172.22.155.100,1433;Initial Catalog=Demo;Integrated Security=False;User ID=sa;Password=London123;Multiple Active Result Sets=True;Connect Timeout=30;Encrypt=True;Trust Server Certificate=True;Application Name=mRemoteNG;Application Intent=ReadOnly"))
|
||||
{
|
||||
connection.Open();
|
||||
}
|
||||
}
|
||||
catch (TypeInitializationException ex)
|
||||
{
|
||||
Console.WriteLine($"Type initialization error: {ex.InnerException?.Message}");
|
||||
}
|
||||
|
||||
|
||||
//await dbConnector.ConnectAsync();
|
||||
return ConnectionTestResult.ConnectionSucceded;
|
||||
}
|
||||
catch (PlatformNotSupportedException ex)
|
||||
{
|
||||
// Log or handle architecture mismatch
|
||||
Console.WriteLine(string.Format(Language.ErrorPlatformNotSupported, ex.Message));
|
||||
return ConnectionTestResult.UnknownError;
|
||||
}
|
||||
catch (DllNotFoundException ex)
|
||||
{
|
||||
// Handle missing native dependencies
|
||||
Console.WriteLine(string.Format(Language.ErrorMissingDependency, ex.Message));
|
||||
return ConnectionTestResult.UnknownError;
|
||||
}
|
||||
catch (BadImageFormatException ex)
|
||||
{
|
||||
// Handle architecture mismatch in native libraries
|
||||
Console.WriteLine(string.Format(Language.ErrorArchitectureMismatch, ex.Message));
|
||||
return ConnectionTestResult.UnknownError;
|
||||
}
|
||||
catch (SqlException sqlException)
|
||||
{
|
||||
if (sqlException.Message.Contains("The server was not found"))
|
||||
return ConnectionTestResult.ServerNotAccessible;
|
||||
if (sqlException.Message.Contains("Cannot open database"))
|
||||
return ConnectionTestResult.UnknownDatabase;
|
||||
if (sqlException.Message.Contains("Login failed for user"))
|
||||
return ConnectionTestResult.CredentialsRejected;
|
||||
return ConnectionTestResult.UnknownError;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Log unexpected errors
|
||||
Console.WriteLine($"Unexpected error: {ex.Message}");
|
||||
return ConnectionTestResult.UnknownError;
|
||||
}
|
||||
*/
|
||||
// }
|
||||
// }
|
||||
}
|
||||
@@ -1,7 +1,8 @@
|
||||
using System.Data;
|
||||
using System.Data.Common;
|
||||
using System.Data.SqlClient;
|
||||
using Microsoft.Data.SqlClient;
|
||||
using System.Threading.Tasks;
|
||||
using static BrightIdeasSoftware.TreeListView;
|
||||
|
||||
// ReSharper disable ArrangeAccessorOwnerBody
|
||||
|
||||
@@ -9,7 +10,7 @@ namespace mRemoteNG.Config.DatabaseConnectors
|
||||
{
|
||||
public class MSSqlDatabaseConnector : IDatabaseConnector
|
||||
{
|
||||
private DbConnection _dbConnection { get; set; } = default(SqlConnection);
|
||||
private DbConnection _dbConnection { get; set; } = default!;
|
||||
private string _dbConnectionString = "";
|
||||
private readonly string _dbHost;
|
||||
private readonly string _dbCatalog;
|
||||
@@ -58,10 +59,17 @@ namespace mRemoteNG.Config.DatabaseConnectors
|
||||
|
||||
_dbConnectionString = new SqlConnectionStringBuilder
|
||||
{
|
||||
ApplicationName = "mRemoteNG",
|
||||
ApplicationIntent = ApplicationIntent.ReadOnly,
|
||||
DataSource = $"{hostParts[0]},{_dbPort}",
|
||||
InitialCatalog = _dbCatalog,
|
||||
UserID = _dbUsername,
|
||||
Password = _dbPassword,
|
||||
IntegratedSecurity = false,
|
||||
Encrypt = true,
|
||||
TrustServerCertificate = true,
|
||||
ConnectTimeout = 30,
|
||||
MultipleActiveResultSets = true
|
||||
}.ToString();
|
||||
}
|
||||
|
||||
|
||||
@@ -10,14 +10,9 @@ using mRemoteNG.Tools;
|
||||
namespace mRemoteNG.Config.Import
|
||||
{
|
||||
[SupportedOSPlatform("windows")]
|
||||
public class PortScanImporter : IConnectionImporter<IEnumerable<ScanHost>>
|
||||
public class PortScanImporter(ProtocolType targetProtocolType) : IConnectionImporter<IEnumerable<ScanHost>>
|
||||
{
|
||||
private readonly ProtocolType _targetProtocolType;
|
||||
|
||||
public PortScanImporter(ProtocolType targetProtocolType)
|
||||
{
|
||||
_targetProtocolType = targetProtocolType;
|
||||
}
|
||||
private readonly ProtocolType _targetProtocolType = targetProtocolType;
|
||||
|
||||
public void Import(IEnumerable<ScanHost> hosts, ContainerInfo destinationContainer)
|
||||
{
|
||||
|
||||
140
mRemoteNG/Config/MachineIdentifier.cs
Normal file
140
mRemoteNG/Config/MachineIdentifier.cs
Normal file
@@ -0,0 +1,140 @@
|
||||
using System;
|
||||
using System.Management;
|
||||
using System.Net.NetworkInformation;
|
||||
using System.Runtime.Versioning;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
|
||||
namespace mRemoteNG.Config.MachineIdentifier
|
||||
{
|
||||
[SupportedOSPlatform("windows")]
|
||||
/// <summary>
|
||||
/// Provides functionality to generate a consistent and unique machine identifier
|
||||
/// based on hardware properties (disk serial number, MAC address, BIOS UUID, and machine name).
|
||||
/// This class is supported only on Windows.
|
||||
/// </summary>
|
||||
public static class MachineIdentifierGenerator
|
||||
{
|
||||
/// <summary>
|
||||
/// Generates a consistent machine identifier by combining hardware-based identifiers
|
||||
/// (disk serial number, MAC address, BIOS UUID, and machine name) and hashing the result.
|
||||
/// </summary>
|
||||
/// <returns>A consistent and unique identifier for the machine.</returns>
|
||||
/// <exception cref="PlatformNotSupportedException">Thrown if the method is called on a non-Windows platform.</exception>
|
||||
public static string GenerateMachineIdentifier()
|
||||
{
|
||||
// Ensure the code runs only on Windows
|
||||
if (!OperatingSystem.IsWindows())
|
||||
{
|
||||
throw new PlatformNotSupportedException("This method is supported only on Windows.");
|
||||
}
|
||||
|
||||
// Retrieve hardware-based identifiers (with fallbacks)
|
||||
string diskId = GetDiskSerialNumber() ?? "NO_DISK_ID";
|
||||
string macAddress = GetMacAddress() ?? "NO_MAC_ADDRESS";
|
||||
string biosUuid = GetBiosUuid() ?? "NO_BIOS_UUID";
|
||||
string machineName = Environment.MachineName;
|
||||
|
||||
// Combine them into a single string
|
||||
string combined = $"{diskId}_{macAddress}_{biosUuid}_{machineName}";
|
||||
|
||||
// Hash the combined string to ensure a fixed length and improve security
|
||||
using (SHA256 sha256 = SHA256.Create())
|
||||
{
|
||||
byte[] hashBytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(combined));
|
||||
return BitConverter.ToString(hashBytes).Replace("-", "").ToLower();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the serial number of the first physical disk using WMI.
|
||||
/// </summary>
|
||||
/// <returns>The disk serial number, or null if the serial number cannot be retrieved.</returns>
|
||||
private static string GetDiskSerialNumber()
|
||||
{
|
||||
try
|
||||
{
|
||||
using (ManagementObjectSearcher searcher = new("SELECT SerialNumber FROM Win32_DiskDrive"))
|
||||
{
|
||||
foreach (ManagementObject wmi_HD in searcher.Get())
|
||||
{
|
||||
if (wmi_HD["SerialNumber"] != null)
|
||||
{
|
||||
string serialNumber = wmi_HD["SerialNumber"].ToString().Trim();
|
||||
if (!string.IsNullOrEmpty(serialNumber))
|
||||
{
|
||||
return serialNumber;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"Error retrieving disk serial number: {ex.Message}");
|
||||
}
|
||||
|
||||
return null; // Return null if the disk serial number cannot be retrieved
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the MAC address of the first active network adapter.
|
||||
/// </summary>
|
||||
/// <returns>The MAC address, or null if the MAC address cannot be retrieved.</returns>
|
||||
private static string GetMacAddress()
|
||||
{
|
||||
try
|
||||
{
|
||||
NetworkInterface[] interfaces = NetworkInterface.GetAllNetworkInterfaces();
|
||||
foreach (NetworkInterface adapter in interfaces)
|
||||
{
|
||||
if (adapter.OperationalStatus == OperationalStatus.Up)
|
||||
{
|
||||
string macAddress = adapter.GetPhysicalAddress().ToString();
|
||||
if (!string.IsNullOrEmpty(macAddress))
|
||||
{
|
||||
return macAddress;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"Error retrieving MAC address: {ex.Message}");
|
||||
}
|
||||
|
||||
return null; // Return null if the MAC address cannot be retrieved
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the BIOS UUID of the machine using WMI.
|
||||
/// </summary>
|
||||
/// <returns>The BIOS UUID, or null if the UUID cannot be retrieved.</returns>
|
||||
private static string GetBiosUuid()
|
||||
{
|
||||
try
|
||||
{
|
||||
using (ManagementObjectSearcher searcher = new ManagementObjectSearcher("SELECT UUID FROM Win32_ComputerSystemProduct"))
|
||||
{
|
||||
foreach (ManagementObject wmi_HD in searcher.Get())
|
||||
{
|
||||
if (wmi_HD["UUID"] != null)
|
||||
{
|
||||
string uuid = wmi_HD["UUID"].ToString().Trim();
|
||||
if (!string.IsNullOrEmpty(uuid))
|
||||
{
|
||||
return uuid;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"Error retrieving BIOS UUID: {ex.Message}");
|
||||
}
|
||||
|
||||
return null; // Return null if the BIOS UUID cannot be retrieved
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,13 +4,8 @@ using mRemoteNG.Connection;
|
||||
|
||||
namespace mRemoteNG.Config.Putty
|
||||
{
|
||||
public class PuttySessionChangedEventArgs : EventArgs
|
||||
public class PuttySessionChangedEventArgs(PuttySessionInfo sessionChanged = null) : EventArgs
|
||||
{
|
||||
public PuttySessionInfo Session { get; set; }
|
||||
|
||||
public PuttySessionChangedEventArgs(PuttySessionInfo sessionChanged = null)
|
||||
{
|
||||
Session = sessionChanged;
|
||||
}
|
||||
public PuttySessionInfo Session { get; set; } = sessionChanged;
|
||||
}
|
||||
}
|
||||
@@ -135,17 +135,17 @@ namespace mRemoteNG.Config.Putty
|
||||
{
|
||||
public static string[] Names => Instance.GetSessionNames();
|
||||
|
||||
public override StandardValuesCollection GetStandardValues(ITypeDescriptorContext context)
|
||||
public override StandardValuesCollection GetStandardValues(ITypeDescriptorContext? context)
|
||||
{
|
||||
return new StandardValuesCollection(Names);
|
||||
}
|
||||
|
||||
public override bool GetStandardValuesExclusive(ITypeDescriptorContext context)
|
||||
public override bool GetStandardValuesExclusive(ITypeDescriptorContext? context)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public override bool GetStandardValuesSupported(ITypeDescriptorContext context)
|
||||
public override bool GetStandardValuesSupported(ITypeDescriptorContext? context)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -113,8 +113,10 @@ namespace mRemoteNG.Config.Serializers.ConnectionSerializers.Csv
|
||||
: "";
|
||||
|
||||
connectionRecord.Password = headers.Contains("Password")
|
||||
? connectionCsv[headers.IndexOf("Password")].ConvertToSecureString()
|
||||
: "".ConvertToSecureString();
|
||||
// ? connectionCsv[headers.IndexOf("Password")].ConvertToSecureString()
|
||||
// : "".ConvertToSecureString();
|
||||
? connectionCsv[headers.IndexOf("Password")]
|
||||
: "";
|
||||
|
||||
connectionRecord.Domain = headers.Contains("Domain")
|
||||
? connectionCsv[headers.IndexOf("Domain")]
|
||||
@@ -166,6 +168,11 @@ namespace mRemoteNG.Config.Serializers.ConnectionSerializers.Csv
|
||||
? connectionCsv[headers.IndexOf("UserField")]
|
||||
: "";
|
||||
|
||||
connectionRecord.EnvironmentTags =
|
||||
headers.Contains("EnvironmentTags")
|
||||
? connectionCsv[headers.IndexOf("EnvironmentTags")]
|
||||
: "";
|
||||
|
||||
connectionRecord.ExtApp = headers.Contains("ExtApp")
|
||||
? connectionCsv[headers.IndexOf("ExtApp")] : "";
|
||||
|
||||
@@ -775,6 +782,12 @@ namespace mRemoteNG.Config.Serializers.ConnectionSerializers.Csv
|
||||
connectionRecord.Inheritance.UserField = value;
|
||||
}
|
||||
|
||||
if (headers.Contains("InheritEnvironmentTags"))
|
||||
{
|
||||
if (bool.TryParse(connectionCsv[headers.IndexOf("InheritEnvironmentTags")], out bool value))
|
||||
connectionRecord.Inheritance.EnvironmentTags = value;
|
||||
}
|
||||
|
||||
if (headers.Contains("InheritFavorite"))
|
||||
{
|
||||
if (bool.TryParse(connectionCsv[headers.IndexOf("InheritFavorite")], out bool value))
|
||||
|
||||
@@ -50,7 +50,7 @@ namespace mRemoteNG.Config.Serializers.ConnectionSerializers.Csv
|
||||
|
||||
private void WriteCsvHeader(StringBuilder sb)
|
||||
{
|
||||
sb.Append("Name;Id;Parent;NodeType;Description;Icon;Panel;");
|
||||
sb.Append("Name;Id;Parent;NodeType;Description;Icon;Panel;TabColor;ConnectionFrameColor;");
|
||||
if (_saveFilter.SaveUsername)
|
||||
sb.Append("Username;");
|
||||
if (_saveFilter.SavePassword)
|
||||
@@ -61,18 +61,18 @@ namespace mRemoteNG.Config.Serializers.ConnectionSerializers.Csv
|
||||
sb.Append("Hostname;Port;VmId;Protocol;SSHTunnelConnectionName;OpeningCommand;SSHOptions;PuttySession;ConnectToConsole;UseCredSsp;UseRestrictedAdmin;UseRCG;UseVmId;UseEnhancedMode;RenderingEngine;RDPAuthenticationLevel;" +
|
||||
"LoadBalanceInfo;Colors;Resolution;AutomaticResize;DisplayWallpaper;DisplayThemes;EnableFontSmoothing;EnableDesktopComposition;DisableFullWindowDrag;DisableMenuAnimations;DisableCursorShadow;DisableCursorBlinking;" +
|
||||
"CacheBitmaps;RedirectDiskDrives;RedirectDiskDrivesCustomRedirectPorts;RedirectPrinters;RedirectClipboard;RedirectSmartCards;RedirectSound;RedirectKeys;" +
|
||||
"PreExtApp;PostExtApp;MacAddress;UserField;ExtApp;Favorite;VNCCompression;VNCEncoding;VNCAuthMode;VNCProxyType;VNCProxyIP;" +
|
||||
"PreExtApp;PostExtApp;MacAddress;UserField;EnvironmentTags;ExtApp;Favorite;VNCCompression;VNCEncoding;VNCAuthMode;VNCProxyType;VNCProxyIP;" +
|
||||
"VNCProxyPort;VNCProxyUsername;VNCProxyPassword;VNCColors;VNCSmartSizeMode;VNCViewOnly;RDGatewayUsageMethod;RDGatewayHostname;" +
|
||||
"RDGatewayUseConnectionCredentials;RDGatewayUsername;RDGatewayPassword;RDGatewayDomain;RDGatewayExternalCredentialProvider;RDGatewayUserViaAPI;RedirectAudioCapture;RdpVersion;RDPStartProgram;RDPStartProgramWorkDir;UserViaAPI;EC2InstanceId;EC2Region;ExternalCredentialProvider;ExternalAddressProvider;");
|
||||
|
||||
if (_saveFilter.SaveInheritance)
|
||||
sb.Append("InheritCacheBitmaps;InheritColors;InheritDescription;InheritDisplayThemes;InheritDisplayWallpaper;" +
|
||||
"InheritEnableFontSmoothing;InheritEnableDesktopComposition;InheritDisableFullWindowDrag;InheritDisableMenuAnimations;InheritDisableCursorShadow;InheritDisableCursorBlinking;InheritDomain;InheritIcon;InheritPanel;InheritPassword;InheritPort;" +
|
||||
"InheritEnableFontSmoothing;InheritEnableDesktopComposition;InheritDisableFullWindowDrag;InheritDisableMenuAnimations;InheritDisableCursorShadow;InheritDisableCursorBlinking;InheritDomain;InheritIcon;InheritPanel;InheritTabColor;InheritConnectionFrameColor;InheritPassword;InheritPort;" +
|
||||
"InheritProtocol;InheritSSHTunnelConnectionName;InheritOpeningCommand;InheritSSHOptions;InheritPuttySession;InheritRedirectDiskDrives;InheritRedirectDiskDrivesCustom;InheritRedirectKeys;InheritRedirectPorts;InheritRedirectPrinters;" +
|
||||
"InheritRedirectClipboard;InheritRedirectSmartCards;InheritRedirectSound;InheritResolution;InheritAutomaticResize;" +
|
||||
"InheritUseConsoleSession;InheritUseCredSsp;InheritUseRestrictedAdmin;InheritUseRCG;InheritUseVmId;InheritUseEnhancedMode;InheritVmId;InheritRenderingEngine;InheritUsername;" +
|
||||
"InheritRDPAuthenticationLevel;InheritLoadBalanceInfo;InheritPreExtApp;InheritPostExtApp;InheritMacAddress;InheritUserField;" +
|
||||
"InheritFavorite;InheritExtApp;InheritVNCCompression;InheritVNCEncoding;InheritVNCAuthMode;InheritVNCProxyType;InheritVNCProxyIP;" +
|
||||
"InheritEnvironmentTags;InheritFavorite;InheritExtApp;InheritVNCCompression;InheritVNCEncoding;InheritVNCAuthMode;InheritVNCProxyType;InheritVNCProxyIP;" +
|
||||
"InheritVNCProxyPort;InheritVNCProxyUsername;InheritVNCProxyPassword;InheritVNCColors;InheritVNCSmartSizeMode;InheritVNCViewOnly;" +
|
||||
"InheritRDGatewayUsageMethod;InheritRDGatewayHostname;InheritRDGatewayUseConnectionCredentials;InheritRDGatewayUsername;" +
|
||||
"InheritRDGatewayPassword;InheritRDGatewayDomain;InheritRDGatewayExternalCredentialProvider;InheritRDGatewayUserViaAPI;InheritRDPAlertIdleTimeout;InheritRDPMinutesToIdleTimeout;InheritSoundQuality;InheritUserViaAPI;" +
|
||||
@@ -106,13 +106,16 @@ namespace mRemoteNG.Config.Serializers.ConnectionSerializers.Csv
|
||||
.Append(FormatForCsv(con.GetTreeNodeType()))
|
||||
.Append(FormatForCsv(con.Description))
|
||||
.Append(FormatForCsv(con.Icon))
|
||||
.Append(FormatForCsv(con.Panel));
|
||||
.Append(FormatForCsv(con.Panel))
|
||||
.Append(FormatForCsv(con.TabColor))
|
||||
.Append(FormatForCsv(con.ConnectionFrameColor));
|
||||
|
||||
if (_saveFilter.SaveUsername)
|
||||
sb.Append(FormatForCsv(con.Username));
|
||||
|
||||
if (_saveFilter.SavePassword)
|
||||
sb.Append(con.Password?.ConvertToUnsecureString() + ";");
|
||||
//sb.Append(con.Password?.ConvertToUnsecureString() + ";");
|
||||
sb.Append(con.Password + ";");
|
||||
|
||||
if (_saveFilter.SaveDomain)
|
||||
sb.Append(FormatForCsv(con.Domain));
|
||||
@@ -158,6 +161,7 @@ namespace mRemoteNG.Config.Serializers.ConnectionSerializers.Csv
|
||||
.Append(FormatForCsv(con.PostExtApp))
|
||||
.Append(FormatForCsv(con.MacAddress))
|
||||
.Append(FormatForCsv(con.UserField))
|
||||
.Append(FormatForCsv(con.EnvironmentTags))
|
||||
.Append(FormatForCsv(con.ExtApp))
|
||||
.Append(FormatForCsv(con.Favorite))
|
||||
.Append(FormatForCsv(con.VNCCompression))
|
||||
@@ -208,6 +212,8 @@ namespace mRemoteNG.Config.Serializers.ConnectionSerializers.Csv
|
||||
.Append(FormatForCsv(con.Inheritance.Domain))
|
||||
.Append(FormatForCsv(con.Inheritance.Icon))
|
||||
.Append(FormatForCsv(con.Inheritance.Panel))
|
||||
.Append(FormatForCsv(con.Inheritance.TabColor))
|
||||
.Append(FormatForCsv(con.Inheritance.ConnectionFrameColor))
|
||||
.Append(FormatForCsv(con.Inheritance.Password))
|
||||
.Append(FormatForCsv(con.Inheritance.Port))
|
||||
.Append(FormatForCsv(con.Inheritance.Protocol))
|
||||
@@ -240,6 +246,7 @@ namespace mRemoteNG.Config.Serializers.ConnectionSerializers.Csv
|
||||
.Append(FormatForCsv(con.Inheritance.PostExtApp))
|
||||
.Append(FormatForCsv(con.Inheritance.MacAddress))
|
||||
.Append(FormatForCsv(con.Inheritance.UserField))
|
||||
.Append(FormatForCsv(con.Inheritance.EnvironmentTags))
|
||||
.Append(FormatForCsv(con.Inheritance.Favorite))
|
||||
.Append(FormatForCsv(con.Inheritance.ExtApp))
|
||||
.Append(FormatForCsv(con.Inheritance.VNCCompression))
|
||||
|
||||
@@ -131,7 +131,8 @@ namespace mRemoteNG.Config.Serializers.ConnectionSerializers.Csv.RemoteDesktopMa
|
||||
Hostname = hostString,
|
||||
Port = port,
|
||||
Username = username,
|
||||
Password = password?.ConvertToSecureString(),
|
||||
//Password = password?.ConvertToSecureString(),
|
||||
Password = password,
|
||||
Domain = domain,
|
||||
Icon = connectionType.IconName ?? "mRemoteNG",
|
||||
Description = description,
|
||||
|
||||
@@ -21,16 +21,10 @@ using mRemoteNG.Tree.Root;
|
||||
namespace mRemoteNG.Config.Serializers.ConnectionSerializers.Sql
|
||||
{
|
||||
[SupportedOSPlatform("windows")]
|
||||
public class DataTableDeserializer : IDeserializer<DataTable, ConnectionTreeModel>
|
||||
public class DataTableDeserializer(ICryptographyProvider cryptographyProvider, SecureString decryptionKey) : IDeserializer<DataTable, ConnectionTreeModel>
|
||||
{
|
||||
private readonly ICryptographyProvider _cryptographyProvider;
|
||||
private readonly SecureString _decryptionKey;
|
||||
|
||||
public DataTableDeserializer(ICryptographyProvider cryptographyProvider, SecureString decryptionKey)
|
||||
{
|
||||
_cryptographyProvider = cryptographyProvider.ThrowIfNull(nameof(cryptographyProvider));
|
||||
_decryptionKey = decryptionKey.ThrowIfNull(nameof(decryptionKey));
|
||||
}
|
||||
private readonly ICryptographyProvider _cryptographyProvider = cryptographyProvider.ThrowIfNull(nameof(cryptographyProvider));
|
||||
private readonly SecureString _decryptionKey = decryptionKey.ThrowIfNull(nameof(decryptionKey));
|
||||
|
||||
public ConnectionTreeModel Deserialize(DataTable table)
|
||||
{
|
||||
@@ -113,7 +107,8 @@ namespace mRemoteNG.Config.Serializers.ConnectionSerializers.Sql
|
||||
connectionInfo.OpeningCommand = (string)dataRow["OpeningCommand"];
|
||||
connectionInfo.Panel = (string)dataRow["Panel"];
|
||||
var pw = dataRow["Password"] as string;
|
||||
connectionInfo.Password = DecryptValue(pw ?? "").ConvertToSecureString();
|
||||
//connectionInfo.Password = DecryptValue(pw ?? "").ConvertToSecureString();
|
||||
connectionInfo.Password = DecryptValue(pw ?? "");
|
||||
connectionInfo.Port = (int)dataRow["Port"];
|
||||
connectionInfo.PostExtApp = (string)dataRow["PostExtApp"];
|
||||
connectionInfo.PreExtApp = (string)dataRow["PreExtApp"];
|
||||
@@ -150,6 +145,7 @@ namespace mRemoteNG.Config.Serializers.ConnectionSerializers.Sql
|
||||
connectionInfo.UseRCG = MiscTools.GetBooleanValue(dataRow["UseRCG"]);
|
||||
connectionInfo.UseRestrictedAdmin = MiscTools.GetBooleanValue(dataRow["UseRestrictedAdmin"]);
|
||||
connectionInfo.UserField = (string)dataRow["UserField"];
|
||||
connectionInfo.EnvironmentTags = dataRow.Table.Columns.Contains("EnvironmentTags") ? (string)dataRow["EnvironmentTags"] : "";
|
||||
connectionInfo.Username = (string)dataRow["Username"];
|
||||
connectionInfo.UseVmId = MiscTools.GetBooleanValue(dataRow["UseVmId"]);
|
||||
connectionInfo.VmId = (string)dataRow["VmId"];
|
||||
@@ -229,6 +225,8 @@ namespace mRemoteNG.Config.Serializers.ConnectionSerializers.Sql
|
||||
connectionInfo.Inheritance.UseRCG = MiscTools.GetBooleanValue(dataRow["InheritUseRCG"]);
|
||||
connectionInfo.Inheritance.UseRestrictedAdmin = MiscTools.GetBooleanValue(dataRow["InheritUseRestrictedAdmin"]);
|
||||
connectionInfo.Inheritance.UserField = MiscTools.GetBooleanValue(dataRow["InheritUserField"]);
|
||||
if (dataRow.Table.Columns.Contains("InheritEnvironmentTags"))
|
||||
connectionInfo.Inheritance.EnvironmentTags = MiscTools.GetBooleanValue(dataRow["InheritEnvironmentTags"]);
|
||||
connectionInfo.Inheritance.Username = MiscTools.GetBooleanValue(dataRow["InheritUsername"]);
|
||||
connectionInfo.Inheritance.UseVmId = MiscTools.GetBooleanValue(dataRow["InheritUseVmId"]);
|
||||
connectionInfo.Inheritance.VmId = MiscTools.GetBooleanValue(dataRow["InheritVmId"]);
|
||||
|
||||
@@ -14,27 +14,20 @@ using mRemoteNG.Tree.Root;
|
||||
namespace mRemoteNG.Config.Serializers.ConnectionSerializers.Sql
|
||||
{
|
||||
[SupportedOSPlatform("windows")]
|
||||
public class DataTableSerializer : ISerializer<ConnectionInfo, DataTable>
|
||||
public class DataTableSerializer(SaveFilter saveFilter, ICryptographyProvider cryptographyProvider, SecureString encryptionKey) : ISerializer<ConnectionInfo, DataTable>
|
||||
{
|
||||
private const int DELETE = 0;
|
||||
private readonly ICryptographyProvider _cryptographyProvider;
|
||||
private readonly SecureString _encryptionKey;
|
||||
private readonly ICryptographyProvider _cryptographyProvider = cryptographyProvider.ThrowIfNull(nameof(cryptographyProvider));
|
||||
private readonly SecureString _encryptionKey = encryptionKey.ThrowIfNull(nameof(encryptionKey));
|
||||
private DataTable _dataTable;
|
||||
private DataTable _sourceDataTable;
|
||||
private readonly Dictionary<string, int> _sourcePrimaryKeyDict = [];
|
||||
private const string TABLE_NAME = "tblCons";
|
||||
private readonly SaveFilter _saveFilter;
|
||||
private readonly SaveFilter _saveFilter = saveFilter.ThrowIfNull(nameof(saveFilter));
|
||||
private int _currentNodeIndex;
|
||||
|
||||
public Version Version { get; } = new Version(3, 0);
|
||||
|
||||
public DataTableSerializer(SaveFilter saveFilter, ICryptographyProvider cryptographyProvider, SecureString encryptionKey)
|
||||
{
|
||||
_saveFilter = saveFilter.ThrowIfNull(nameof(saveFilter));
|
||||
_cryptographyProvider = cryptographyProvider.ThrowIfNull(nameof(cryptographyProvider));
|
||||
_encryptionKey = encryptionKey.ThrowIfNull(nameof(encryptionKey));
|
||||
}
|
||||
|
||||
public void SetSourceDataTable(DataTable sourceDataTable)
|
||||
{
|
||||
_sourceDataTable = sourceDataTable;
|
||||
@@ -184,6 +177,7 @@ namespace mRemoteNG.Config.Serializers.ConnectionSerializers.Sql
|
||||
dataTable.Columns.Add("InheritUseRestrictedAdmin", typeof(bool));
|
||||
dataTable.Columns.Add("InheritUseVmId", typeof(bool));
|
||||
dataTable.Columns.Add("InheritUserField", typeof(bool));
|
||||
dataTable.Columns.Add("InheritEnvironmentTags", typeof(bool));
|
||||
dataTable.Columns.Add("InheritUserViaAPI", typeof(bool));
|
||||
dataTable.Columns.Add("InheritUsername", typeof(bool));
|
||||
dataTable.Columns.Add("InheritVNCAuthMode", typeof(bool));
|
||||
@@ -247,6 +241,7 @@ namespace mRemoteNG.Config.Serializers.ConnectionSerializers.Sql
|
||||
dataTable.Columns.Add("UseRestrictedAdmin", typeof(bool));
|
||||
dataTable.Columns.Add("UseVmId", typeof(bool));
|
||||
dataTable.Columns.Add("UserField", typeof(string));
|
||||
dataTable.Columns.Add("EnvironmentTags", typeof(string));
|
||||
dataTable.Columns.Add("UserViaAPI", typeof(string));
|
||||
dataTable.Columns.Add("Username", typeof(string));
|
||||
dataTable.Columns.Add("VNCAuthMode", typeof(string));
|
||||
@@ -519,7 +514,10 @@ namespace mRemoteNG.Config.Serializers.ConnectionSerializers.Sql
|
||||
dataRow["InheritVNCViewOnly"].Equals(false);
|
||||
}
|
||||
|
||||
bool pwd = dataRow["Password"].Equals(_saveFilter.SavePassword ? _cryptographyProvider.Encrypt(connectionInfo.Password?.ConvertToUnsecureString(), _encryptionKey) : "") &&
|
||||
//bool pwd = dataRow["Password"].Equals(_saveFilter.SavePassword ? _cryptographyProvider.Encrypt(connectionInfo.Password?.ConvertToUnsecureString(), _encryptionKey) : "") &&
|
||||
// dataRow["VNCProxyPassword"].Equals(_cryptographyProvider.Encrypt(connectionInfo.VNCProxyPassword, _encryptionKey)) &&
|
||||
// dataRow["RDGatewayPassword"].Equals(_cryptographyProvider.Encrypt(connectionInfo.RDGatewayPassword, _encryptionKey));
|
||||
bool pwd = dataRow["Password"].Equals(_saveFilter.SavePassword ? _cryptographyProvider.Encrypt(connectionInfo.Password, _encryptionKey) : "") &&
|
||||
dataRow["VNCProxyPassword"].Equals(_cryptographyProvider.Encrypt(connectionInfo.VNCProxyPassword, _encryptionKey)) &&
|
||||
dataRow["RDGatewayPassword"].Equals(_cryptographyProvider.Encrypt(connectionInfo.RDGatewayPassword, _encryptionKey));
|
||||
return !(pwd && isFieldNotChange && isInheritanceFieldNotChange);
|
||||
@@ -572,10 +570,11 @@ namespace mRemoteNG.Config.Serializers.ConnectionSerializers.Sql
|
||||
dataRow["MacAddress"] = connectionInfo.MacAddress;
|
||||
dataRow["Name"] = connectionInfo.Name;
|
||||
dataRow["OpeningCommand"] = connectionInfo.OpeningCommand;
|
||||
dataRow["OpeningCommand"] = connectionInfo.OpeningCommand;
|
||||
// dataRow["OpeningCommand"] = connectionInfo.OpeningCommand; dublicate?
|
||||
dataRow["Panel"] = connectionInfo.Panel;
|
||||
dataRow["ParentID"] = connectionInfo.Parent?.ConstantID ?? "";
|
||||
dataRow["Password"] = _saveFilter.SavePassword ? _cryptographyProvider.Encrypt(connectionInfo.Password?.ConvertToUnsecureString(), _encryptionKey) : "";
|
||||
//dataRow["Password"] = _saveFilter.SavePassword ? _cryptographyProvider.Encrypt(connectionInfo.Password?.ConvertToUnsecureString(), _encryptionKey) : "";
|
||||
dataRow["Password"] = _saveFilter.SavePassword ? _cryptographyProvider.Encrypt(connectionInfo.Password, _encryptionKey) : "";
|
||||
dataRow["Port"] = connectionInfo.Port;
|
||||
dataRow["PositionID"] = _currentNodeIndex;
|
||||
dataRow["PostExtApp"] = connectionInfo.PostExtApp;
|
||||
@@ -615,6 +614,7 @@ namespace mRemoteNG.Config.Serializers.ConnectionSerializers.Sql
|
||||
dataRow["UseRestrictedAdmin"] = connectionInfo.UseRestrictedAdmin;
|
||||
dataRow["UseVmId"] = connectionInfo.UseVmId;
|
||||
dataRow["UserField"] = connectionInfo.UserField;
|
||||
dataRow["EnvironmentTags"] = connectionInfo.EnvironmentTags;
|
||||
dataRow["Username"] = _saveFilter.SaveUsername ? connectionInfo.Username : "";
|
||||
dataRow["VNCAuthMode"] = connectionInfo.VNCAuthMode;
|
||||
dataRow["VNCColors"] = connectionInfo.VNCColors;
|
||||
@@ -693,6 +693,7 @@ namespace mRemoteNG.Config.Serializers.ConnectionSerializers.Sql
|
||||
dataRow["InheritUseRestrictedAdmin"] = connectionInfo.Inheritance.UseRestrictedAdmin;
|
||||
dataRow["InheritUseVmId"] = connectionInfo.Inheritance.UseVmId;
|
||||
dataRow["InheritUserField"] = connectionInfo.Inheritance.UserField;
|
||||
dataRow["InheritEnvironmentTags"] = connectionInfo.Inheritance.EnvironmentTags;
|
||||
dataRow["InheritUserViaAPI"] = connectionInfo.Inheritance.UserViaAPI;
|
||||
dataRow["InheritUsername"] = connectionInfo.Inheritance.Username;
|
||||
dataRow["InheritVNCAuthMode"] = connectionInfo.Inheritance.VNCAuthMode;
|
||||
@@ -770,6 +771,7 @@ namespace mRemoteNG.Config.Serializers.ConnectionSerializers.Sql
|
||||
dataRow["InheritUseRCG"] = false;
|
||||
dataRow["InheritUseRestrictedAdmin"] = false;
|
||||
dataRow["InheritUserField"] = false;
|
||||
dataRow["InheritEnvironmentTags"] = false;
|
||||
dataRow["InheritUserViaAPI"] = false;
|
||||
dataRow["InheritUsername"] = false;
|
||||
dataRow["InheritVNCAuthMode"] = false;
|
||||
|
||||
@@ -99,9 +99,22 @@ namespace mRemoteNG.Config.Serializers.ConnectionSerializers.Sql
|
||||
if (rootTreeNode != null)
|
||||
{
|
||||
cmd = databaseConnector.DbCommand(
|
||||
"INSERT INTO tblRoot (Name, Export, Protected, ConfVersion) VALUES('" +
|
||||
MiscTools.PrepareValueForDB(rootTreeNode.Name) + "', 0, '" + strProtected + "','" +
|
||||
ConnectionsFileInfo.ConnectionFileVersion + "')");
|
||||
"INSERT INTO tblRoot (Name, Export, Protected, ConfVersion) VALUES(@Name, 0, @Protected, @ConfVersion)");
|
||||
|
||||
DbParameter nameParam = cmd.CreateParameter();
|
||||
nameParam.ParameterName = "@Name";
|
||||
nameParam.Value = rootTreeNode.Name;
|
||||
cmd.Parameters.Add(nameParam);
|
||||
|
||||
DbParameter protectedParam = cmd.CreateParameter();
|
||||
protectedParam.ParameterName = "@Protected";
|
||||
protectedParam.Value = strProtected;
|
||||
cmd.Parameters.Add(protectedParam);
|
||||
|
||||
DbParameter confVersionParam = cmd.CreateParameter();
|
||||
confVersionParam.ParameterName = "@ConfVersion";
|
||||
confVersionParam.Value = ConnectionsFileInfo.ConnectionFileVersion.ToString();
|
||||
cmd.Parameters.Add(confVersionParam);
|
||||
|
||||
cmd.ExecuteNonQuery();
|
||||
}
|
||||
@@ -111,6 +124,22 @@ namespace mRemoteNG.Config.Serializers.ConnectionSerializers.Sql
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsValidTableName(string tableName)
|
||||
{
|
||||
// Table names should only contain alphanumeric characters and underscores
|
||||
// This prevents SQL injection when table names must be used directly in queries
|
||||
if (string.IsNullOrWhiteSpace(tableName))
|
||||
return false;
|
||||
|
||||
foreach (char c in tableName)
|
||||
{
|
||||
if (!char.IsLetterOrDigit(c) && c != '_')
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool DoesDbTableExist(IDatabaseConnector databaseConnector, string tableName)
|
||||
{
|
||||
bool exists;
|
||||
@@ -119,7 +148,18 @@ namespace mRemoteNG.Config.Serializers.ConnectionSerializers.Sql
|
||||
{
|
||||
// ANSI SQL way. Works in PostgreSQL, MSSQL, MySQL.
|
||||
string database_name = Properties.OptionsDBsPage.Default.SQLDatabaseName;
|
||||
DbCommand cmd = databaseConnector.DbCommand("select case when exists((select * from information_schema.tables where table_name = '" + tableName + "' and table_schema='"+ database_name + "')) then 1 else 0 end");
|
||||
DbCommand cmd = databaseConnector.DbCommand("select case when exists((select * from information_schema.tables where table_name = @TableName and table_schema = @DatabaseName)) then 1 else 0 end");
|
||||
|
||||
DbParameter tableNameParam = cmd.CreateParameter();
|
||||
tableNameParam.ParameterName = "@TableName";
|
||||
tableNameParam.Value = tableName;
|
||||
cmd.Parameters.Add(tableNameParam);
|
||||
|
||||
DbParameter databaseNameParam = cmd.CreateParameter();
|
||||
databaseNameParam.ParameterName = "@DatabaseName";
|
||||
databaseNameParam.Value = database_name;
|
||||
cmd.Parameters.Add(databaseNameParam);
|
||||
|
||||
short cmdResult = Convert.ToInt16(cmd.ExecuteScalar());
|
||||
exists = (cmdResult == 1);
|
||||
}
|
||||
@@ -128,9 +168,18 @@ namespace mRemoteNG.Config.Serializers.ConnectionSerializers.Sql
|
||||
try
|
||||
{
|
||||
// Other RDBMS. Graceful degradation
|
||||
exists = true;
|
||||
DbCommand cmdOthers = databaseConnector.DbCommand("select 1 from " + tableName + " where 1 = 0");
|
||||
cmdOthers.ExecuteNonQuery();
|
||||
// Note: Table names cannot be parameterized in standard SQL.
|
||||
// Validate tableName to prevent SQL injection
|
||||
if (!IsValidTableName(tableName))
|
||||
{
|
||||
exists = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
exists = true;
|
||||
DbCommand cmdOthers = databaseConnector.DbCommand($"select 1 from {tableName} where 1 = 0");
|
||||
cmdOthers.ExecuteNonQuery();
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
||||
@@ -61,7 +61,8 @@ namespace mRemoteNG.Config.Serializers.ConnectionSerializers.Xml
|
||||
: new XAttribute("Domain", ""));
|
||||
|
||||
if (_saveFilter.SavePassword && !connectionInfo.Inheritance.Password)
|
||||
element.Add(new XAttribute("Password", _cryptographyProvider.Encrypt(connectionInfo.Password.ConvertToUnsecureString(), _encryptionKey)));
|
||||
//element.Add(new XAttribute("Password", _cryptographyProvider.Encrypt(connectionInfo.Password.ConvertToUnsecureString(), _encryptionKey)));
|
||||
element.Add(new XAttribute("Password", _cryptographyProvider.Encrypt(connectionInfo.Password, _encryptionKey)));
|
||||
else
|
||||
element.Add(new XAttribute("Password", ""));
|
||||
|
||||
|
||||
@@ -11,23 +11,16 @@ namespace mRemoteNG.Config.Serializers.ConnectionSerializers.Xml
|
||||
{
|
||||
// ReSharper disable once InconsistentNaming
|
||||
[SupportedOSPlatform("windows")]
|
||||
public class XmlConnectionNodeSerializer27 : ISerializer<ConnectionInfo, XElement>
|
||||
public class XmlConnectionNodeSerializer27(ICryptographyProvider cryptographyProvider,
|
||||
SecureString encryptionKey,
|
||||
SaveFilter saveFilter) : ISerializer<ConnectionInfo, XElement>
|
||||
{
|
||||
private readonly ICryptographyProvider _cryptographyProvider;
|
||||
private readonly SecureString _encryptionKey;
|
||||
private readonly SaveFilter _saveFilter;
|
||||
private readonly ICryptographyProvider _cryptographyProvider = cryptographyProvider ?? throw new ArgumentNullException(nameof(cryptographyProvider));
|
||||
private readonly SecureString _encryptionKey = encryptionKey ?? throw new ArgumentNullException(nameof(encryptionKey));
|
||||
private readonly SaveFilter _saveFilter = saveFilter ?? throw new ArgumentNullException(nameof(saveFilter));
|
||||
|
||||
public Version Version { get; } = new Version(2, 7);
|
||||
|
||||
public XmlConnectionNodeSerializer27(ICryptographyProvider cryptographyProvider,
|
||||
SecureString encryptionKey,
|
||||
SaveFilter saveFilter)
|
||||
{
|
||||
_cryptographyProvider = cryptographyProvider ?? throw new ArgumentNullException(nameof(cryptographyProvider));
|
||||
_encryptionKey = encryptionKey ?? throw new ArgumentNullException(nameof(encryptionKey));
|
||||
_saveFilter = saveFilter ?? throw new ArgumentNullException(nameof(saveFilter));
|
||||
}
|
||||
|
||||
public XElement Serialize(ConnectionInfo connectionInfo)
|
||||
{
|
||||
XElement element = new(XName.Get("Node", ""));
|
||||
@@ -62,7 +55,8 @@ namespace mRemoteNG.Config.Serializers.ConnectionSerializers.Xml
|
||||
: new XAttribute("Domain", ""));
|
||||
|
||||
if (_saveFilter.SavePassword && !connectionInfo.Inheritance.Password)
|
||||
element.Add(new XAttribute("Password", _cryptographyProvider.Encrypt(connectionInfo.Password.ConvertToUnsecureString(), _encryptionKey)));
|
||||
//element.Add(new XAttribute("Password", _cryptographyProvider.Encrypt(connectionInfo.Password.ConvertToUnsecureString(), _encryptionKey)));
|
||||
element.Add(new XAttribute("Password", _cryptographyProvider.Encrypt(connectionInfo.Password, _encryptionKey)));
|
||||
else
|
||||
element.Add(new XAttribute("Password", ""));
|
||||
}
|
||||
|
||||
@@ -11,23 +11,16 @@ namespace mRemoteNG.Config.Serializers.ConnectionSerializers.Xml
|
||||
{
|
||||
// ReSharper disable once InconsistentNaming
|
||||
[SupportedOSPlatform("windows")]
|
||||
public class XmlConnectionNodeSerializer28 : ISerializer<ConnectionInfo, XElement>
|
||||
public class XmlConnectionNodeSerializer28(ICryptographyProvider cryptographyProvider,
|
||||
SecureString encryptionKey,
|
||||
SaveFilter saveFilter) : ISerializer<ConnectionInfo, XElement>
|
||||
{
|
||||
private readonly ICryptographyProvider _cryptographyProvider;
|
||||
private readonly SecureString _encryptionKey;
|
||||
private readonly SaveFilter _saveFilter;
|
||||
private readonly ICryptographyProvider _cryptographyProvider = cryptographyProvider ?? throw new ArgumentNullException(nameof(cryptographyProvider));
|
||||
private readonly SecureString _encryptionKey = encryptionKey ?? throw new ArgumentNullException(nameof(encryptionKey));
|
||||
private readonly SaveFilter _saveFilter = saveFilter ?? throw new ArgumentNullException(nameof(saveFilter));
|
||||
|
||||
public Version Version { get; } = new Version(2, 8);
|
||||
|
||||
public XmlConnectionNodeSerializer28(ICryptographyProvider cryptographyProvider,
|
||||
SecureString encryptionKey,
|
||||
SaveFilter saveFilter)
|
||||
{
|
||||
_cryptographyProvider = cryptographyProvider ?? throw new ArgumentNullException(nameof(cryptographyProvider));
|
||||
_encryptionKey = encryptionKey ?? throw new ArgumentNullException(nameof(encryptionKey));
|
||||
_saveFilter = saveFilter ?? throw new ArgumentNullException(nameof(saveFilter));
|
||||
}
|
||||
|
||||
public XElement Serialize(ConnectionInfo connectionInfo)
|
||||
{
|
||||
XElement element = new(XName.Get("Node", ""));
|
||||
@@ -49,6 +42,8 @@ namespace mRemoteNG.Config.Serializers.ConnectionSerializers.Xml
|
||||
element.Add(new XAttribute("Descr", connectionInfo.Description));
|
||||
element.Add(new XAttribute("Icon", connectionInfo.Icon));
|
||||
element.Add(new XAttribute("Panel", connectionInfo.Panel));
|
||||
element.Add(new XAttribute("TabColor", connectionInfo.TabColor));
|
||||
element.Add(new XAttribute("ConnectionFrameColor", connectionInfo.ConnectionFrameColor));
|
||||
element.Add(new XAttribute("Id", connectionInfo.ConstantID));
|
||||
|
||||
if (!Runtime.UseCredentialManager)
|
||||
@@ -62,7 +57,8 @@ namespace mRemoteNG.Config.Serializers.ConnectionSerializers.Xml
|
||||
: new XAttribute("Domain", ""));
|
||||
|
||||
if (_saveFilter.SavePassword && !connectionInfo.Inheritance.Password)
|
||||
element.Add(new XAttribute("Password", _cryptographyProvider.Encrypt(connectionInfo.Password?.ConvertToUnsecureString(), _encryptionKey)));
|
||||
//element.Add(new XAttribute("Password", _cryptographyProvider.Encrypt(connectionInfo.Password?.ConvertToUnsecureString(), _encryptionKey)));
|
||||
element.Add(new XAttribute("Password", _cryptographyProvider.Encrypt(connectionInfo.Password, _encryptionKey)));
|
||||
else
|
||||
element.Add(new XAttribute("Password", ""));
|
||||
}
|
||||
@@ -109,6 +105,7 @@ namespace mRemoteNG.Config.Serializers.ConnectionSerializers.Xml
|
||||
element.Add(new XAttribute("PostExtApp", connectionInfo.PostExtApp));
|
||||
element.Add(new XAttribute("MacAddress", connectionInfo.MacAddress));
|
||||
element.Add(new XAttribute("UserField", connectionInfo.UserField));
|
||||
element.Add(new XAttribute("EnvironmentTags", connectionInfo.EnvironmentTags));
|
||||
element.Add(new XAttribute("Favorite", connectionInfo.Favorite));
|
||||
element.Add(new XAttribute("ExtApp", connectionInfo.ExtApp));
|
||||
element.Add(new XAttribute("StartProgram", connectionInfo.RDPStartProgram));
|
||||
@@ -157,6 +154,11 @@ namespace mRemoteNG.Config.Serializers.ConnectionSerializers.Xml
|
||||
element.Add(new XAttribute("EC2Region", connectionInfo.EC2Region));
|
||||
element.Add(new XAttribute("ExternalCredentialProvider", connectionInfo.ExternalCredentialProvider));
|
||||
element.Add(new XAttribute("ExternalAddressProvider", connectionInfo.ExternalAddressProvider));
|
||||
|
||||
// Vault/OpenBao specific
|
||||
element.Add(new XAttribute("VaultOpenbaoMount", connectionInfo.VaultOpenbaoMount ?? string.Empty));
|
||||
element.Add(new XAttribute("VaultOpenbaoRole", connectionInfo.VaultOpenbaoRole ?? string.Empty));
|
||||
element.Add(new XAttribute("VaultOpenbaoSecretEngine", connectionInfo.VaultOpenbaoSecretEngine));
|
||||
}
|
||||
|
||||
private void SetInheritanceAttributes(XContainer element, IInheritable connectionInfo)
|
||||
@@ -193,6 +195,10 @@ namespace mRemoteNG.Config.Serializers.ConnectionSerializers.Xml
|
||||
element.Add(new XAttribute("InheritIcon", inheritance.Icon.ToString().ToLowerInvariant()));
|
||||
if (inheritance.Panel)
|
||||
element.Add(new XAttribute("InheritPanel", inheritance.Panel.ToString().ToLowerInvariant()));
|
||||
if (inheritance.TabColor)
|
||||
element.Add(new XAttribute("InheritTabColor", inheritance.TabColor.ToString().ToLowerInvariant()));
|
||||
if (inheritance.ConnectionFrameColor)
|
||||
element.Add(new XAttribute("InheritConnectionFrameColor", inheritance.ConnectionFrameColor.ToString().ToLowerInvariant()));
|
||||
if (inheritance.Password)
|
||||
element.Add(new XAttribute("InheritPassword", inheritance.Password.ToString().ToLowerInvariant()));
|
||||
if (inheritance.Port)
|
||||
@@ -257,6 +263,8 @@ namespace mRemoteNG.Config.Serializers.ConnectionSerializers.Xml
|
||||
element.Add(new XAttribute("InheritMacAddress", inheritance.MacAddress.ToString().ToLowerInvariant()));
|
||||
if (inheritance.UserField)
|
||||
element.Add(new XAttribute("InheritUserField", inheritance.UserField.ToString().ToLowerInvariant()));
|
||||
if (inheritance.EnvironmentTags)
|
||||
element.Add(new XAttribute("InheritEnvironmentTags", inheritance.EnvironmentTags.ToString().ToLowerInvariant()));
|
||||
if (inheritance.Favorite)
|
||||
element.Add(new XAttribute("InheritFavorite", inheritance.Favorite.ToString().ToLowerInvariant()));
|
||||
if (inheritance.ExtApp)
|
||||
|
||||
@@ -94,8 +94,7 @@ namespace mRemoteNG.Config.Serializers.ConnectionSerializers.Xml
|
||||
connections = _decryptor.LegacyFullFileDecrypt(connections);
|
||||
if (connections != "")
|
||||
{
|
||||
_xmlDocument = new XmlDocument();
|
||||
_xmlDocument.LoadXml(connections);
|
||||
_xmlDocument = SecureXmlHelper.LoadXmlFromString(connections);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -120,8 +119,7 @@ namespace mRemoteNG.Config.Serializers.ConnectionSerializers.Xml
|
||||
|
||||
private void InitializeRootNode(XmlElement connectionsRootElement)
|
||||
{
|
||||
string rootNodeName = connectionsRootElement?.Attributes["Name"]?.Value.Trim();
|
||||
_rootNodeInfo.Name = rootNodeName;
|
||||
_rootNodeInfo.Name = connectionsRootElement?.Attributes["Name"]?.Value.Trim();
|
||||
}
|
||||
|
||||
private void CreateDecryptor(RootNodeInfo rootNodeInfo, XmlElement connectionsRootElement = null)
|
||||
@@ -217,8 +215,8 @@ namespace mRemoteNG.Config.Serializers.ConnectionSerializers.Xml
|
||||
if (!Runtime.UseCredentialManager || _confVersion <= 2.6) // 0.2 - 2.6
|
||||
{
|
||||
connectionInfo.Username = xmlnode.GetAttributeAsString("Username");
|
||||
//connectionInfo.Password = _decryptor.Decrypt(xmlnode.GetAttributeAsString("Password"));
|
||||
connectionInfo.Password = _decryptor.Decrypt(xmlnode.GetAttributeAsString("Password")).ConvertToSecureString();
|
||||
connectionInfo.Password = _decryptor.Decrypt(xmlnode.GetAttributeAsString("Password"));
|
||||
//connectionInfo.Password = _decryptor.Decrypt(xmlnode.GetAttributeAsString("Password")).ConvertToSecureString();
|
||||
connectionInfo.Domain = xmlnode.GetAttributeAsString("Domain");
|
||||
}
|
||||
}
|
||||
@@ -329,6 +327,8 @@ namespace mRemoteNG.Config.Serializers.ConnectionSerializers.Xml
|
||||
connectionInfo.Inheritance.DisplayWallpaper = xmlnode.GetAttributeAsBool("InheritDisplayWallpaper");
|
||||
connectionInfo.Inheritance.Icon = xmlnode.GetAttributeAsBool("InheritIcon");
|
||||
connectionInfo.Inheritance.Panel = xmlnode.GetAttributeAsBool("InheritPanel");
|
||||
connectionInfo.Inheritance.TabColor = xmlnode.GetAttributeAsBool("InheritTabColor");
|
||||
connectionInfo.Inheritance.ConnectionFrameColor = xmlnode.GetAttributeAsBool("InheritConnectionFrameColor");
|
||||
connectionInfo.Inheritance.Port = xmlnode.GetAttributeAsBool("InheritPort");
|
||||
connectionInfo.Inheritance.Protocol = xmlnode.GetAttributeAsBool("InheritProtocol");
|
||||
connectionInfo.Inheritance.PuttySession = xmlnode.GetAttributeAsBool("InheritPuttySession");
|
||||
@@ -351,6 +351,8 @@ namespace mRemoteNG.Config.Serializers.ConnectionSerializers.Xml
|
||||
|
||||
connectionInfo.Icon = xmlnode.GetAttributeAsString("Icon");
|
||||
connectionInfo.Panel = xmlnode.GetAttributeAsString("Panel");
|
||||
connectionInfo.TabColor = xmlnode.GetAttributeAsString("TabColor");
|
||||
connectionInfo.ConnectionFrameColor = xmlnode.GetAttributeAsEnum<ConnectionFrameColor>("ConnectionFrameColor");
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -516,6 +518,9 @@ namespace mRemoteNG.Config.Serializers.ConnectionSerializers.Xml
|
||||
connectionInfo.UserViaAPI = xmlnode.GetAttributeAsString("UserViaAPI");
|
||||
connectionInfo.Inheritance.UserViaAPI = xmlnode.GetAttributeAsBool("InheritUserViaAPI");
|
||||
connectionInfo.ExternalAddressProvider = xmlnode.GetAttributeAsEnum("ExternalAddressProvider", ExternalAddressProvider.None);
|
||||
connectionInfo.VaultOpenbaoMount = xmlnode.GetAttributeAsString("VaultOpenbaoMount");
|
||||
connectionInfo.VaultOpenbaoRole = xmlnode.GetAttributeAsString("VaultOpenbaoRole");
|
||||
connectionInfo.VaultOpenbaoSecretEngine = xmlnode.GetAttributeAsEnum("VaultOpenbaoSecretEngine", VaultOpenbaoSecretEngine.Kv);
|
||||
connectionInfo.EC2InstanceId = xmlnode.GetAttributeAsString("EC2InstanceId");
|
||||
connectionInfo.EC2Region = xmlnode.GetAttributeAsString("EC2Region");
|
||||
connectionInfo.UseRestrictedAdmin = xmlnode.GetAttributeAsBool("UseRestrictedAdmin");
|
||||
@@ -534,6 +539,8 @@ namespace mRemoteNG.Config.Serializers.ConnectionSerializers.Xml
|
||||
connectionInfo.RedirectDiskDrives = xmlnode.GetAttributeAsEnum<RDPDiskDrives>("RedirectDiskDrives");
|
||||
connectionInfo.RedirectDiskDrivesCustom = xmlnode.GetAttributeAsString("RedirectDiskDrivesCustom");
|
||||
connectionInfo.Inheritance.RedirectDiskDrivesCustom = xmlnode.GetAttributeAsBool("InheritRedirectDiskDrivesCustom");
|
||||
connectionInfo.EnvironmentTags = xmlnode.GetAttributeAsString("EnvironmentTags");
|
||||
connectionInfo.Inheritance.EnvironmentTags = xmlnode.GetAttributeAsBool("InheritEnvironmentTags");
|
||||
break;
|
||||
|
||||
case >= 0.5:
|
||||
|
||||
@@ -12,17 +12,11 @@ using mRemoteNG.Tree.Root;
|
||||
namespace mRemoteNG.Config.Serializers.ConnectionSerializers.Xml
|
||||
{
|
||||
[SupportedOSPlatform("windows")]
|
||||
public class XmlConnectionsDocumentCompiler
|
||||
public class XmlConnectionsDocumentCompiler(ICryptographyProvider cryptographyProvider, ISerializer<ConnectionInfo, XElement> connectionNodeSerializer)
|
||||
{
|
||||
private readonly ICryptographyProvider _cryptographyProvider;
|
||||
private readonly ICryptographyProvider _cryptographyProvider = cryptographyProvider ?? throw new ArgumentNullException(nameof(cryptographyProvider));
|
||||
private SecureString _encryptionKey;
|
||||
private readonly ISerializer<ConnectionInfo, XElement> _connectionNodeSerializer;
|
||||
|
||||
public XmlConnectionsDocumentCompiler(ICryptographyProvider cryptographyProvider, ISerializer<ConnectionInfo, XElement> connectionNodeSerializer)
|
||||
{
|
||||
_cryptographyProvider = cryptographyProvider ?? throw new ArgumentNullException(nameof(cryptographyProvider));
|
||||
_connectionNodeSerializer = connectionNodeSerializer ?? throw new ArgumentNullException(nameof(connectionNodeSerializer));
|
||||
}
|
||||
private readonly ISerializer<ConnectionInfo, XElement> _connectionNodeSerializer = connectionNodeSerializer ?? throw new ArgumentNullException(nameof(connectionNodeSerializer));
|
||||
|
||||
public XDocument CompileDocument(ConnectionTreeModel connectionTreeModel, bool fullFileEncryption)
|
||||
{
|
||||
|
||||
@@ -4,14 +4,9 @@ using mRemoteNG.Security;
|
||||
|
||||
namespace mRemoteNG.Config.Serializers.ConnectionSerializers.Xml
|
||||
{
|
||||
public class XmlConnectionsDocumentEncryptor
|
||||
public class XmlConnectionsDocumentEncryptor(ICryptographyProvider cryptographyProvider)
|
||||
{
|
||||
private readonly ICryptographyProvider _cryptographyProvider;
|
||||
|
||||
public XmlConnectionsDocumentEncryptor(ICryptographyProvider cryptographyProvider)
|
||||
{
|
||||
_cryptographyProvider = cryptographyProvider;
|
||||
}
|
||||
private readonly ICryptographyProvider _cryptographyProvider = cryptographyProvider;
|
||||
|
||||
public XDocument EncryptDocument(XDocument documentToEncrypt, SecureString encryptionKey)
|
||||
{
|
||||
|
||||
@@ -14,22 +14,16 @@ using mRemoteNG.Tree.Root;
|
||||
namespace mRemoteNG.Config.Serializers.ConnectionSerializers.Xml
|
||||
{
|
||||
[SupportedOSPlatform("windows")]
|
||||
public class XmlConnectionsSerializer : ISerializer<ConnectionTreeModel, string>,
|
||||
public class XmlConnectionsSerializer(ICryptographyProvider cryptographyProvider,
|
||||
ISerializer<ConnectionInfo, XElement> connectionNodeSerializer) : ISerializer<ConnectionTreeModel, string>,
|
||||
ISerializer<ConnectionInfo, string>
|
||||
{
|
||||
private readonly ICryptographyProvider _cryptographyProvider;
|
||||
private readonly ISerializer<ConnectionInfo, XElement> _connectionNodeSerializer;
|
||||
private readonly ICryptographyProvider _cryptographyProvider = cryptographyProvider;
|
||||
private readonly ISerializer<ConnectionInfo, XElement> _connectionNodeSerializer = connectionNodeSerializer;
|
||||
|
||||
public Version Version => _connectionNodeSerializer.Version;
|
||||
public bool UseFullEncryption { get; set; }
|
||||
|
||||
public XmlConnectionsSerializer(ICryptographyProvider cryptographyProvider,
|
||||
ISerializer<ConnectionInfo, XElement> connectionNodeSerializer)
|
||||
{
|
||||
_cryptographyProvider = cryptographyProvider;
|
||||
_connectionNodeSerializer = connectionNodeSerializer;
|
||||
}
|
||||
|
||||
public string Serialize(ConnectionTreeModel connectionTreeModel)
|
||||
{
|
||||
RootNodeInfo rootNode = (RootNodeInfo)connectionTreeModel.RootNodes.First(node => node is RootNodeInfo);
|
||||
|
||||
@@ -28,7 +28,7 @@ namespace mRemoteNG.Config.Serializers.ConnectionSerializers.Xml
|
||||
private XAttribute CreateProtectedAttribute(RootNodeInfo rootNodeInfo, ICryptographyProvider cryptographyProvider)
|
||||
{
|
||||
XAttribute attribute = new(XName.Get("Protected"), "");
|
||||
string plainText = rootNodeInfo.Password ? "ThisIsProtected" : "ThisIsNotProtected";
|
||||
string plainText = (rootNodeInfo.PasswordString != rootNodeInfo.DefaultPassword) ? "ThisIsProtected" : "ThisIsNotProtected";
|
||||
System.Security.SecureString encryptionPassword = rootNodeInfo.PasswordString.ConvertToSecureString();
|
||||
attribute.Value = cryptographyProvider.Encrypt(plainText, encryptionPassword);
|
||||
return attribute;
|
||||
|
||||
@@ -10,20 +10,54 @@ using mRemoteNG.Tools;
|
||||
using mRemoteNG.Tree;
|
||||
using mRemoteNG.Tree.Root;
|
||||
using mRemoteNG.Resources.Language;
|
||||
using mRemoteNG.Security;
|
||||
using System.Runtime.Versioning;
|
||||
|
||||
namespace mRemoteNG.Config.Serializers.MiscSerializers
|
||||
{
|
||||
[SupportedOSPlatform("windows")]
|
||||
public class ActiveDirectoryDeserializer
|
||||
public class ActiveDirectoryDeserializer(string ldapPath, bool importSubOu)
|
||||
{
|
||||
private readonly string _ldapPath;
|
||||
private readonly bool _importSubOu;
|
||||
private readonly string _ldapPath = SanitizeLdapPath(ldapPath.ThrowIfNullOrEmpty(nameof(ldapPath)));
|
||||
private readonly bool _importSubOu = importSubOu;
|
||||
|
||||
public ActiveDirectoryDeserializer(string ldapPath, bool importSubOu)
|
||||
private static string SanitizeLdapPath(string ldapPath)
|
||||
{
|
||||
_ldapPath = ldapPath.ThrowIfNullOrEmpty(nameof(ldapPath));
|
||||
_importSubOu = importSubOu;
|
||||
// Validate the LDAP path format
|
||||
if (!LdapPathSanitizer.IsValidDistinguishedNameFormat(ldapPath))
|
||||
{
|
||||
throw new ArgumentException("Invalid LDAP path format", nameof(ldapPath));
|
||||
}
|
||||
|
||||
// For LDAP paths (URIs like LDAP://...), we need to sanitize the DN portion
|
||||
// If it starts with LDAP:// or LDAPS://, extract and sanitize the DN part
|
||||
if (ldapPath.StartsWith("LDAP://", StringComparison.OrdinalIgnoreCase) ||
|
||||
ldapPath.StartsWith("LDAPS://", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
int schemeEndIndex = ldapPath.IndexOf("://", StringComparison.OrdinalIgnoreCase) + 3;
|
||||
if (schemeEndIndex < ldapPath.Length)
|
||||
{
|
||||
// Find the server/domain part (before the first /)
|
||||
int pathStartIndex = ldapPath.IndexOf('/', schemeEndIndex);
|
||||
if (pathStartIndex > 0)
|
||||
{
|
||||
string scheme = ldapPath.Substring(0, schemeEndIndex);
|
||||
string serverPart = ldapPath.Substring(schemeEndIndex, pathStartIndex - schemeEndIndex);
|
||||
string dnPart = ldapPath.Substring(pathStartIndex + 1);
|
||||
|
||||
// Sanitize the DN part
|
||||
string sanitizedDn = LdapPathSanitizer.SanitizeDistinguishedName(dnPart);
|
||||
return scheme + serverPart + "/" + sanitizedDn;
|
||||
}
|
||||
}
|
||||
// If no DN part found, return the path as-is (just the server)
|
||||
return ldapPath;
|
||||
}
|
||||
else
|
||||
{
|
||||
// For plain DN strings, sanitize directly
|
||||
return LdapPathSanitizer.SanitizeDistinguishedName(ldapPath);
|
||||
}
|
||||
}
|
||||
|
||||
public ConnectionTreeModel Deserialize()
|
||||
|
||||
@@ -10,14 +10,9 @@ using mRemoteNG.Tree.Root;
|
||||
namespace mRemoteNG.Config.Serializers.MiscSerializers
|
||||
{
|
||||
[SupportedOSPlatform("windows")]
|
||||
public class PortScanDeserializer : IDeserializer<IEnumerable<ScanHost>, ConnectionTreeModel>
|
||||
public class PortScanDeserializer(ProtocolType targetProtocolType) : IDeserializer<IEnumerable<ScanHost>, ConnectionTreeModel>
|
||||
{
|
||||
private readonly ProtocolType _targetProtocolType;
|
||||
|
||||
public PortScanDeserializer(ProtocolType targetProtocolType)
|
||||
{
|
||||
_targetProtocolType = targetProtocolType;
|
||||
}
|
||||
private readonly ProtocolType _targetProtocolType = targetProtocolType;
|
||||
|
||||
public ConnectionTreeModel Deserialize(IEnumerable<ScanHost> scannedHosts)
|
||||
{
|
||||
@@ -66,6 +61,10 @@ namespace mRemoteNG.Config.Serializers.MiscSerializers
|
||||
if (host.Vnc)
|
||||
finalProtocol = ProtocolType.VNC;
|
||||
break;
|
||||
case ProtocolType.ARD:
|
||||
if (host.Vnc)
|
||||
finalProtocol = ProtocolType.ARD;
|
||||
break;
|
||||
default:
|
||||
protocolValid = false;
|
||||
break;
|
||||
|
||||
@@ -20,8 +20,7 @@ namespace mRemoteNG.Config.Serializers.MiscSerializers
|
||||
RootNodeInfo root = new(RootNodeType.Connection);
|
||||
connectionTreeModel.AddRootNode(root);
|
||||
|
||||
XmlDocument xmlDocument = new();
|
||||
xmlDocument.LoadXml(puttycmConnectionsXml);
|
||||
XmlDocument xmlDocument = SecureXmlHelper.LoadXmlFromString(puttycmConnectionsXml);
|
||||
|
||||
XmlNode configurationNode = xmlDocument.SelectSingleNode("/configuration");
|
||||
|
||||
@@ -134,7 +133,8 @@ namespace mRemoteNG.Config.Serializers.MiscSerializers
|
||||
|
||||
XmlNode loginNode = xmlNode.SelectSingleNode("./login");
|
||||
connectionInfo.Username = loginNode?.SelectSingleNode("login")?.InnerText;
|
||||
connectionInfo.Password = loginNode?.SelectSingleNode("password")?.InnerText.ConvertToSecureString();
|
||||
//connectionInfo.Password = loginNode?.SelectSingleNode("password")?.InnerText.ConvertToSecureString();
|
||||
connectionInfo.Password = loginNode?.SelectSingleNode("password")?.InnerText;
|
||||
// ./prompt
|
||||
|
||||
// ./timeout/connectiontimeout
|
||||
|
||||
@@ -101,6 +101,9 @@ namespace mRemoteNG.Config.Serializers.MiscSerializers
|
||||
case "allow desktop composition":
|
||||
connectionInfo.EnableDesktopComposition = value == "1";
|
||||
break;
|
||||
case "keyboardhook":
|
||||
connectionInfo.RedirectKeys = value == "1";
|
||||
break;
|
||||
case "redirectsmartcards":
|
||||
connectionInfo.RedirectSmartCards = value == "1";
|
||||
break;
|
||||
@@ -156,6 +159,34 @@ namespace mRemoteNG.Config.Serializers.MiscSerializers
|
||||
case "gatewayhostname":
|
||||
connectionInfo.RDGatewayHostname = value;
|
||||
break;
|
||||
case "gatewaycredentialssource":
|
||||
switch(value)
|
||||
{
|
||||
case "0":
|
||||
connectionInfo.RDGatewayUseConnectionCredentials = RDGatewayUseConnectionCredentials.ExternalCredentialProvider;
|
||||
break;
|
||||
case "1":
|
||||
connectionInfo.RDGatewayUseConnectionCredentials = RDGatewayUseConnectionCredentials.SmartCard;
|
||||
break;
|
||||
case "2":
|
||||
connectionInfo.RDGatewayUseConnectionCredentials = RDGatewayUseConnectionCredentials.Yes;
|
||||
break;
|
||||
case "3":
|
||||
// Both 3 and 4 require that the user enter gateway credentials manually
|
||||
connectionInfo.RDGatewayUseConnectionCredentials = RDGatewayUseConnectionCredentials.No;
|
||||
break;
|
||||
case "4":
|
||||
// Both 3 and 4 require that the user enter gateway credentials manually
|
||||
connectionInfo.RDGatewayUseConnectionCredentials = RDGatewayUseConnectionCredentials.No;
|
||||
break;
|
||||
case "5":
|
||||
connectionInfo.RDGatewayUseConnectionCredentials = RDGatewayUseConnectionCredentials.AccessToken;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case "gatewayaccesstoken":
|
||||
connectionInfo.RDGatewayAccessToken = value;
|
||||
break;
|
||||
case "alternate shell":
|
||||
connectionInfo.RDPStartProgram = value;
|
||||
break;
|
||||
|
||||
@@ -26,9 +26,7 @@ namespace mRemoteNG.Config.Serializers.MiscSerializers
|
||||
ConnectionTreeModel connectionTreeModel = new();
|
||||
RootNodeInfo root = new(RootNodeType.Connection);
|
||||
|
||||
XmlDocument xmlDocument = new();
|
||||
xmlDocument.LoadXml(rdcmConnectionsXml);
|
||||
|
||||
XmlDocument xmlDocument = SecureXmlHelper.LoadXmlFromString(rdcmConnectionsXml);
|
||||
|
||||
XmlNode rdcManNode = xmlDocument.SelectSingleNode("/RDCMan");
|
||||
VerifySchemaVersion(rdcManNode);
|
||||
@@ -164,12 +162,15 @@ namespace mRemoteNG.Config.Serializers.MiscSerializers
|
||||
if (_schemaVersion == 1) // Version 2.2 allows clear text passwords
|
||||
{
|
||||
connectionInfo.Password = passwordNode?.Attributes?["storeAsClearText"]?.Value == "True"
|
||||
? passwordNode.InnerText.ConvertToSecureString()
|
||||
: DecryptRdcManPassword(passwordNode?.InnerText).ConvertToSecureString();
|
||||
//? passwordNode.InnerText.ConvertToSecureString()
|
||||
//: DecryptRdcManPassword(passwordNode?.InnerText).ConvertToSecureString();
|
||||
? passwordNode.InnerText
|
||||
: DecryptRdcManPassword(passwordNode?.InnerText);
|
||||
}
|
||||
else
|
||||
{
|
||||
connectionInfo.Password = DecryptRdcManPassword(passwordNode?.InnerText).ConvertToSecureString();
|
||||
//connectionInfo.Password = DecryptRdcManPassword(passwordNode?.InnerText).ConvertToSecureString();
|
||||
connectionInfo.Password = DecryptRdcManPassword(passwordNode?.InnerText);
|
||||
}
|
||||
|
||||
connectionInfo.Domain = logonCredentialsNode.SelectSingleNode("./domain")?.InnerText ?? string.Empty;
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using mRemoteNG.Connection;
|
||||
using mRemoteNG.Connection.Protocol;
|
||||
using mRemoteNG.Container;
|
||||
using mRemoteNG.Security;
|
||||
using mRemoteNG.Tree;
|
||||
using mRemoteNG.Tree.Root;
|
||||
using System;
|
||||
@@ -22,8 +23,7 @@ namespace mRemoteNG.Config.Serializers.MiscSerializers
|
||||
RootNodeInfo root = new(RootNodeType.Connection);
|
||||
connectionTreeModel.AddRootNode(root);
|
||||
|
||||
XmlDocument xmlDocument = new();
|
||||
xmlDocument.LoadXml(content);
|
||||
XmlDocument xmlDocument = SecureXmlHelper.LoadXmlFromString(content);
|
||||
|
||||
XmlNode sessionsNode = xmlDocument.SelectSingleNode("/VanDyke/key[@name=\"Sessions\"]");
|
||||
|
||||
|
||||
@@ -9,16 +9,11 @@ using System.Runtime.Versioning;
|
||||
namespace mRemoteNG.Config.Serializers.Versioning
|
||||
{
|
||||
[SupportedOSPlatform("windows")]
|
||||
public class SqlDatabaseVersionVerifier
|
||||
public class SqlDatabaseVersionVerifier(IDatabaseConnector databaseConnector)
|
||||
{
|
||||
private readonly Version _currentSupportedVersion = new(3, 0);
|
||||
|
||||
private readonly IDatabaseConnector _databaseConnector;
|
||||
|
||||
public SqlDatabaseVersionVerifier(IDatabaseConnector databaseConnector)
|
||||
{
|
||||
_databaseConnector = databaseConnector ?? throw new ArgumentNullException(nameof(databaseConnector));
|
||||
}
|
||||
private readonly IDatabaseConnector _databaseConnector = databaseConnector ?? throw new ArgumentNullException(nameof(databaseConnector));
|
||||
|
||||
public bool VerifyDatabaseVersion(Version dbVersion)
|
||||
{
|
||||
|
||||
@@ -7,14 +7,9 @@ using System.Runtime.Versioning;
|
||||
namespace mRemoteNG.Config.Serializers.Versioning
|
||||
{
|
||||
[SupportedOSPlatform("windows")]
|
||||
public class SqlVersion22To23Upgrader : IVersionUpgrader
|
||||
public class SqlVersion22To23Upgrader(IDatabaseConnector databaseConnector) : IVersionUpgrader
|
||||
{
|
||||
private readonly IDatabaseConnector _databaseConnector;
|
||||
|
||||
public SqlVersion22To23Upgrader(IDatabaseConnector databaseConnector)
|
||||
{
|
||||
_databaseConnector = databaseConnector ?? throw new ArgumentNullException(nameof(databaseConnector));
|
||||
}
|
||||
private readonly IDatabaseConnector _databaseConnector = databaseConnector ?? throw new ArgumentNullException(nameof(databaseConnector));
|
||||
|
||||
public bool CanUpgrade(Version currentVersion)
|
||||
{
|
||||
|
||||
@@ -7,14 +7,9 @@ using System.Runtime.Versioning;
|
||||
namespace mRemoteNG.Config.Serializers.Versioning
|
||||
{
|
||||
[SupportedOSPlatform("windows")]
|
||||
public class SqlVersion23To24Upgrader : IVersionUpgrader
|
||||
public class SqlVersion23To24Upgrader(IDatabaseConnector databaseConnector) : IVersionUpgrader
|
||||
{
|
||||
private readonly IDatabaseConnector _databaseConnector;
|
||||
|
||||
public SqlVersion23To24Upgrader(IDatabaseConnector databaseConnector)
|
||||
{
|
||||
_databaseConnector = databaseConnector ?? throw new ArgumentNullException(nameof(databaseConnector));
|
||||
}
|
||||
private readonly IDatabaseConnector _databaseConnector = databaseConnector ?? throw new ArgumentNullException(nameof(databaseConnector));
|
||||
|
||||
public bool CanUpgrade(Version currentVersion)
|
||||
{
|
||||
|
||||
@@ -7,14 +7,9 @@ using System.Runtime.Versioning;
|
||||
namespace mRemoteNG.Config.Serializers.Versioning
|
||||
{
|
||||
[SupportedOSPlatform("windows")]
|
||||
public class SqlVersion24To25Upgrader : IVersionUpgrader
|
||||
public class SqlVersion24To25Upgrader(IDatabaseConnector databaseConnector) : IVersionUpgrader
|
||||
{
|
||||
private readonly IDatabaseConnector _databaseConnector;
|
||||
|
||||
public SqlVersion24To25Upgrader(IDatabaseConnector databaseConnector)
|
||||
{
|
||||
_databaseConnector = databaseConnector ?? throw new ArgumentNullException(nameof(databaseConnector));
|
||||
}
|
||||
private readonly IDatabaseConnector _databaseConnector = databaseConnector ?? throw new ArgumentNullException(nameof(databaseConnector));
|
||||
|
||||
public bool CanUpgrade(Version currentVersion)
|
||||
{
|
||||
|
||||
@@ -7,14 +7,9 @@ using System.Runtime.Versioning;
|
||||
namespace mRemoteNG.Config.Serializers.Versioning
|
||||
{
|
||||
[SupportedOSPlatform("windows")]
|
||||
public class SqlVersion25To26Upgrader : IVersionUpgrader
|
||||
public class SqlVersion25To26Upgrader(IDatabaseConnector databaseConnector) : IVersionUpgrader
|
||||
{
|
||||
private readonly IDatabaseConnector _databaseConnector;
|
||||
|
||||
public SqlVersion25To26Upgrader(IDatabaseConnector databaseConnector)
|
||||
{
|
||||
_databaseConnector = databaseConnector ?? throw new ArgumentNullException(nameof(databaseConnector));
|
||||
}
|
||||
private readonly IDatabaseConnector _databaseConnector = databaseConnector ?? throw new ArgumentNullException(nameof(databaseConnector));
|
||||
|
||||
public bool CanUpgrade(Version currentVersion)
|
||||
{
|
||||
|
||||
@@ -2,20 +2,15 @@
|
||||
using mRemoteNG.Config.DatabaseConnectors;
|
||||
using mRemoteNG.Messages;
|
||||
using System;
|
||||
using System.Data.SqlClient;
|
||||
using Microsoft.Data.SqlClient;
|
||||
using System.Runtime.Versioning;
|
||||
|
||||
namespace mRemoteNG.Config.Serializers.Versioning
|
||||
{
|
||||
[SupportedOSPlatform("windows")]
|
||||
public class SqlVersion26To27Upgrader : IVersionUpgrader
|
||||
public class SqlVersion26To27Upgrader(IDatabaseConnector databaseConnector) : IVersionUpgrader
|
||||
{
|
||||
private readonly IDatabaseConnector _databaseConnector;
|
||||
|
||||
public SqlVersion26To27Upgrader(IDatabaseConnector databaseConnector)
|
||||
{
|
||||
_databaseConnector = databaseConnector ?? throw new ArgumentNullException(nameof(databaseConnector));
|
||||
}
|
||||
private readonly IDatabaseConnector _databaseConnector = databaseConnector ?? throw new ArgumentNullException(nameof(databaseConnector));
|
||||
|
||||
public bool CanUpgrade(Version currentVersion)
|
||||
{
|
||||
|
||||
@@ -3,20 +3,15 @@ using mRemoteNG.Config.DatabaseConnectors;
|
||||
using mRemoteNG.Messages;
|
||||
using System;
|
||||
using System.Data.Common;
|
||||
using System.Data.SqlClient;
|
||||
using Microsoft.Data.SqlClient;
|
||||
using System.Runtime.Versioning;
|
||||
|
||||
namespace mRemoteNG.Config.Serializers.Versioning
|
||||
{
|
||||
[SupportedOSPlatform("windows")]
|
||||
public class SqlVersion27To28Upgrader : IVersionUpgrader
|
||||
public class SqlVersion27To28Upgrader(IDatabaseConnector databaseConnector) : IVersionUpgrader
|
||||
{
|
||||
private readonly IDatabaseConnector _databaseConnector;
|
||||
|
||||
public SqlVersion27To28Upgrader(IDatabaseConnector databaseConnector)
|
||||
{
|
||||
_databaseConnector = databaseConnector ?? throw new ArgumentNullException(nameof(databaseConnector));
|
||||
}
|
||||
private readonly IDatabaseConnector _databaseConnector = databaseConnector ?? throw new ArgumentNullException(nameof(databaseConnector));
|
||||
|
||||
public bool CanUpgrade(Version currentVersion)
|
||||
{
|
||||
|
||||
@@ -8,15 +8,10 @@ using System.Runtime.Versioning;
|
||||
namespace mRemoteNG.Config.Serializers.Versioning
|
||||
{
|
||||
[SupportedOSPlatform("windows")]
|
||||
public class SqlVersion28To29Upgrader : IVersionUpgrader
|
||||
public class SqlVersion28To29Upgrader(IDatabaseConnector databaseConnector) : IVersionUpgrader
|
||||
{
|
||||
private readonly Version _version = new(2, 9);
|
||||
private readonly IDatabaseConnector _databaseConnector;
|
||||
|
||||
public SqlVersion28To29Upgrader(IDatabaseConnector databaseConnector)
|
||||
{
|
||||
_databaseConnector = databaseConnector ?? throw new ArgumentNullException(nameof(databaseConnector));
|
||||
}
|
||||
private readonly IDatabaseConnector _databaseConnector = databaseConnector ?? throw new ArgumentNullException(nameof(databaseConnector));
|
||||
|
||||
public bool CanUpgrade(Version currentVersion)
|
||||
{
|
||||
|
||||
@@ -8,15 +8,10 @@ using System.Runtime.Versioning;
|
||||
namespace mRemoteNG.Config.Serializers.Versioning
|
||||
{
|
||||
[SupportedOSPlatform("windows")]
|
||||
public class SqlVersion29To30Upgrader : IVersionUpgrader
|
||||
public class SqlVersion29To30Upgrader(IDatabaseConnector databaseConnector) : IVersionUpgrader
|
||||
{
|
||||
private readonly Version _version = new(3, 0);
|
||||
private readonly IDatabaseConnector _databaseConnector;
|
||||
|
||||
public SqlVersion29To30Upgrader(IDatabaseConnector databaseConnector)
|
||||
{
|
||||
_databaseConnector = databaseConnector ?? throw new ArgumentNullException(nameof(databaseConnector));
|
||||
}
|
||||
private readonly IDatabaseConnector _databaseConnector = databaseConnector ?? throw new ArgumentNullException(nameof(databaseConnector));
|
||||
|
||||
public bool CanUpgrade(Version currentVersion)
|
||||
{
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user