From e93fc8b3ec603c4dcf469e90370b8dd4251dae3f Mon Sep 17 00:00:00 2001 From: Peter Palotas Date: Wed, 29 Aug 2018 10:58:10 +0200 Subject: [PATCH 1/3] Added .editorconfig to keep formatting of files. Added .vs directory to .gitignore. --- .gitignore | 3 +++ src/.editorconfig | 14 ++++++++++++++ 2 files changed, 17 insertions(+) create mode 100644 src/.editorconfig diff --git a/.gitignore b/.gitignore index 92ab569..81ddf2d 100644 --- a/.gitignore +++ b/.gitignore @@ -53,6 +53,9 @@ deploy/ [Bb]in/ [Oo]bj/ +# Visual Studio 2015/2017 cache/options directory +.vs/ + # MSTest test Results [Tt]est[Rr]esult*/ [Bb]uild[Ll]og.* diff --git a/src/.editorconfig b/src/.editorconfig new file mode 100644 index 0000000..8d8d91c --- /dev/null +++ b/src/.editorconfig @@ -0,0 +1,14 @@ +# EditorConfig is awesome: https://EditorConfig.org + +# top-most EditorConfig file +root = true + +# Unix-style newlines with a newline ending every file +[*] +end_of_line = crlf +insert_final_newline = true + +[*.cs] +indent_size: 2 +indent_style: space + From c5a1b47d557493ebeda6721fb159129d0fd25fd0 Mon Sep 17 00:00:00 2001 From: Peter Palotas Date: Wed, 29 Aug 2018 11:00:46 +0200 Subject: [PATCH 2/3] Added a few new tests to test signing in-place, and to a new location with and without .pdb-files present. --- .../Brutal.Dev.StrongNameSigner.Tests.csproj | 20 ++-- .../SignAssemblyTests.cs | 95 +++++++++++++++++- ...al.Dev.StrongNameSigner.TestAssembly.A.dll | Bin 5120 -> 5120 bytes ...al.Dev.StrongNameSigner.TestAssembly.A.pdb | Bin 0 -> 11776 bytes ...al.Dev.StrongNameSigner.TestAssembly.B.dll | Bin 5120 -> 5120 bytes ...al.Dev.StrongNameSigner.TestAssembly.B.pdb | Bin 0 -> 11776 bytes src/Brutal.Dev.StrongNameSigner.sln | 1 + 7 files changed, 108 insertions(+), 8 deletions(-) create mode 100644 src/Brutal.Dev.StrongNameSigner.Tests/TestAssemblies/Brutal.Dev.StrongNameSigner.TestAssembly.A.pdb create mode 100644 src/Brutal.Dev.StrongNameSigner.Tests/TestAssemblies/Brutal.Dev.StrongNameSigner.TestAssembly.B.pdb diff --git a/src/Brutal.Dev.StrongNameSigner.Tests/Brutal.Dev.StrongNameSigner.Tests.csproj b/src/Brutal.Dev.StrongNameSigner.Tests/Brutal.Dev.StrongNameSigner.Tests.csproj index 4b66aa9..b19179a 100644 --- a/src/Brutal.Dev.StrongNameSigner.Tests/Brutal.Dev.StrongNameSigner.Tests.csproj +++ b/src/Brutal.Dev.StrongNameSigner.Tests/Brutal.Dev.StrongNameSigner.Tests.csproj @@ -73,12 +73,6 @@ - - - {947eecc6-5ebc-4d2a-bac4-8e88b5bafe84} - Brutal.Dev.StrongNameSigner - - @@ -153,9 +147,15 @@ PreserveNewest - + + PreserveNewest + + PreserveNewest + + PreserveNewest + @@ -165,6 +165,12 @@ PreserveNewest + + + {947eecc6-5ebc-4d2a-bac4-8e88b5bafe84} + Brutal.Dev.StrongNameSigner + + diff --git a/src/Brutal.Dev.StrongNameSigner.Tests/SignAssemblyTests.cs b/src/Brutal.Dev.StrongNameSigner.Tests/SignAssemblyTests.cs index 6db6251..6be8588 100644 --- a/src/Brutal.Dev.StrongNameSigner.Tests/SignAssemblyTests.cs +++ b/src/Brutal.Dev.StrongNameSigner.Tests/SignAssemblyTests.cs @@ -3,7 +3,6 @@ using System; using System.IO; using System.Security.Cryptography; -using System.Text; using System.Reflection; namespace Brutal.Dev.StrongNameSigner.Tests @@ -13,6 +12,7 @@ public class SignAssemblyTests { private static readonly string TestAssemblyDirectory = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), @"TestAssemblies"); + [Test] public void SignAssembly_Public_API_Test() { @@ -144,6 +144,99 @@ public void SignAssembly_Should_Reassemble_NET_40_x64_Assembly_Correctly() info.Is64BitOnly.ShouldBe(true); info.IsSigned.ShouldBe(true); } + + + [Test] + public void SignAssembly_InPlaceWithPdb_Should_Succeed() + { + var tempDir = Path.Combine(TestAssemblyDirectory, Guid.NewGuid().ToString("N")); + Directory.CreateDirectory(tempDir); + try + { + string targetAssemblyPath = Path.Combine(tempDir, "Brutal.Dev.StrongNameSigner.TestAssembly.A.dll"); + File.Copy(Path.Combine(TestAssemblyDirectory, "Brutal.Dev.StrongNameSigner.TestAssembly.A.dll"), targetAssemblyPath); + File.Copy(Path.Combine(TestAssemblyDirectory, "Brutal.Dev.StrongNameSigner.TestAssembly.A.pdb"), Path.Combine(tempDir, "Brutal.Dev.StrongNameSigner.TestAssembly.A.pdb")); + + SigningHelper.SignAssembly(targetAssemblyPath); + var info = SigningHelper.GetAssemblyInfo(targetAssemblyPath); + Assert.IsTrue(info.IsSigned); + } + finally + { + Directory.Delete(tempDir, true); + } + } + + [Test] + public void SignAssembly_NewLocationWithPdb_Should_Succeed() + { + var tempDir = Path.Combine(TestAssemblyDirectory, Guid.NewGuid().ToString("N")); + Directory.CreateDirectory(tempDir); + var outDir = Path.Combine(tempDir, "out"); + Directory.CreateDirectory(outDir); + try + { + string sourceAssemblyPath = Path.Combine(tempDir, "Brutal.Dev.StrongNameSigner.TestAssembly.A.dll"); + File.Copy(Path.Combine(TestAssemblyDirectory, "Brutal.Dev.StrongNameSigner.TestAssembly.A.dll"), sourceAssemblyPath); + File.Copy(Path.Combine(TestAssemblyDirectory, "Brutal.Dev.StrongNameSigner.TestAssembly.A.pdb"), Path.Combine(tempDir, "Brutal.Dev.StrongNameSigner.TestAssembly.A.pdb")); + + SigningHelper.SignAssembly(sourceAssemblyPath, null, outDir); + string outAssembly = Path.Combine(outDir, Path.GetFileName(sourceAssemblyPath)); + Assert.IsTrue(File.Exists(outAssembly)); + Assert.IsTrue(File.Exists(Path.ChangeExtension(outAssembly, ".pdb"))); + var info = SigningHelper.GetAssemblyInfo(outAssembly); + Assert.IsTrue(info.IsSigned); + } + finally + { + Directory.Delete(tempDir, true); + } + } + + [Test] + public void SignAssembly_NewLocationWithoutPdb_Should_Succeed() + { + var tempDir = Path.Combine(TestAssemblyDirectory, Guid.NewGuid().ToString("N")); + Directory.CreateDirectory(tempDir); + var outDir = Path.Combine(tempDir, "out"); + Directory.CreateDirectory(outDir); + try + { + string sourceAssemblyPath = Path.Combine(tempDir, "Brutal.Dev.StrongNameSigner.TestAssembly.A.dll"); + File.Copy(Path.Combine(TestAssemblyDirectory, "Brutal.Dev.StrongNameSigner.TestAssembly.A.dll"), sourceAssemblyPath); + + SigningHelper.SignAssembly(sourceAssemblyPath, null, outDir); + string outAssembly = Path.Combine(outDir, Path.GetFileName(sourceAssemblyPath)); + Assert.IsTrue(File.Exists(outAssembly)); + var info = SigningHelper.GetAssemblyInfo(outAssembly); + Assert.IsTrue(info.IsSigned); + } + finally + { + Directory.Delete(tempDir, true); + } + } + + [Test] + public void SignAssembly_InPlaceWithoutPdb_Should_Succeed() + { + var tempDir = Path.Combine(TestAssemblyDirectory, Guid.NewGuid().ToString("N")); + Directory.CreateDirectory(tempDir); + try + { + string targetAssemblyPath = Path.Combine(tempDir, "Brutal.Dev.StrongNameSigner.TestAssembly.A.dll"); + File.Copy(Path.Combine(TestAssemblyDirectory, "Brutal.Dev.StrongNameSigner.TestAssembly.A.dll"), targetAssemblyPath); + + SigningHelper.SignAssembly(targetAssemblyPath); + var info = SigningHelper.GetAssemblyInfo(targetAssemblyPath); + Assert.IsTrue(info.IsSigned); + + } + finally + { + Directory.Delete(tempDir, true); + } + } } } diff --git a/src/Brutal.Dev.StrongNameSigner.Tests/TestAssemblies/Brutal.Dev.StrongNameSigner.TestAssembly.A.dll b/src/Brutal.Dev.StrongNameSigner.Tests/TestAssemblies/Brutal.Dev.StrongNameSigner.TestAssembly.A.dll index 33b0a0a0ef1c5d8890eb3aa316773e37183df0e0..2c98f2b25af05ff11877bc1d3150b20c18845ead 100644 GIT binary patch delta 1280 zcmYk6OKe+36o&t~wXYxXqi#rWlPGm+HE~@NTm|!tP@R~jQGlj65Bi{r?OZvR+PUC+ zLourI*tD(+66sl#Mb#p)p=uF`NHp6bq#_|9kjjQtgg`>5QUyCu7n~W#RgdlO%z4b5 znS0N8HL;rbxY76ZiSsA1EnC|V=DfoJU`<{q2r47q~^zIWK^YQy%oW`5`3~DehFDMo{Y)( z`DmIK5qy@kCin!>+RgN1KXWGzD!!nYRUB75rg&Vj!0h0S$ozK|>xyek9Tz26SY6Zh zU=VFcG23}}gXqQ-Gmf{I2k;SdNbx1bQRSZiu|KaoClyP$$(}R#g=ynAbiuXSeCT|h zZIPWOUujFCam=Z4^KrD?8PIpGVF9}2+aDc4)h=1Jidn?ik~yDsxI>CdjzNFx9etx# zb;h*KV#RpmN7lBDYOzwEGM$P+Nqt{X+aOuX?Gh}KH#6PRFJRRj!~sGcYAcO-!@7P@oKGu9JK5)R^heA>Z$m*1CcZ`R=!* zlMed3HE&{L#=j}(Q~#S33cMUjJ@x`qE;7%Q6vzKFvDa(Iad2FaEQc>(kiNWJC3OsX@2wG z|IO^YdAqZXR3r87<@ovOe{$Gq_rT+wtP=>3pti(|&1*}cy(wVH^^X8iPct3~7Jwl6 zTSGv=M;d6WBzBwJoh}ghgL8k%u`{xhuuR%KrtgU z4B;tZ^TE|Envl2%oaDgI)Dt&J6+z&cY;qh&AHZ_?-Bw)YR7wacbu=q2PW(JvDv_?VZEMw%v2sFp3{8CR zO^QwGIVCLiD3ikSM#%Sf*A1s`X^&TKE3wgOv%09;ddYaKW2UYz|2GTmXD@1&t(!IR zo;NwxN^&Hl7b`W>cJ#81j8>{wD#iajsHM>zm@}6}uP-6)dt+i*8K4vIo1elIa$4Ea z94bpkOxEW2``?V-_+V}J%N70l;X}`qp?>jtezAA_-a1WSo)}{x7u-t-r(IK}K*-}` zJ#*r?dQ^Ptd&{pf_+-7s6<=zxRrB`0!u>@1DX1o?)8#sz%jD)hK7ISqpV{oDdZc{T zIq6??Y&vj$NwaF2mCu;vdR41Ac78^8j@C=!C-sl6S^BrMTTT4t@8gA3Grnsh z5V*-iD0nDztD_wTZgq$kgCDV&co0nRZ7v47cC&`4cYQ`24BcUuMQ`^z?A*pD-32ZR Ve#0MY{ihnwZG?ONV$G+0{{li(2|NG* diff --git a/src/Brutal.Dev.StrongNameSigner.Tests/TestAssemblies/Brutal.Dev.StrongNameSigner.TestAssembly.A.pdb b/src/Brutal.Dev.StrongNameSigner.Tests/TestAssemblies/Brutal.Dev.StrongNameSigner.TestAssembly.A.pdb new file mode 100644 index 0000000000000000000000000000000000000000..c2dc30224b58b7ce93b80e0f41dbd2980e67cd8f GIT binary patch literal 11776 zcmeHNUuYaf7@xgfOs+|q-l=EWh#YF6ZQ8v%4JH&Jq={*RUTb?1YCWZQmrZ)~a*MaC zHg5+)`yfauK2%Xef-mZW)Ivc`$%7R91B$Iah!5g}P!(%Mun*Gn_nW`6B^93SrOjiu6~v3@g==v=*iBrQMz`77>1(d9kp z4v*<;14LlHs;QYHFd%+tX7`)k_x8_{#sKHBwKagP5!GrCyjop_tENVdz>UVgOOJme z5BYVzy<#SfllzZ6WuW%Gq|tW6#L%O1yaX}4^brgmkgt#o0k zWR){cIiJ~He$LGn%@ON)vtDkFTaG*AIM#HoIAacFhRnPp#KgAD6qGqqSqaLVYszw- zvI!`wmzyq>WtFmQCQD_^MpD2oS_z`XdT%O2@gFZeKPHR{bbN7oZ?7|W-uSmkJ=^1m zSdNH#mZP*0pzf^?Ux;uapiGURg|epQ5dpqgrq~>&HL=DGthmw5cwftb(^f;KCgP+UCX zWZ|R#r4OHeU0j?9@O{wKcoB$*4RZeEyk`h+f{%(#zPiLi2~x80eAga%pM7o?2emfb zOs?x!egrtiFNBgZ?{61fFL#K&2Rq@`VIT%<0rmlknER}gH^0rRI`4GYe?F`vbW)Bwbk+u`pvfF z%;}V6i@60>d+VAR?9L;0>QP7<;FXnhN%=zGe`a7w9Lf2ivC^b1;&Kl!;y4V5AGER) zRykL+^M|~O$wJX`;)}iIXJXFa_0&RgFF2EBu1-w0k#t{8-kFS(^oJ=g4!3ya9wi2R z1MvR2f+D+{cw-LJ)EZH X6M@^8k15&`9RVEy9RVGIC5*s73z3kP literal 0 HcmV?d00001 diff --git a/src/Brutal.Dev.StrongNameSigner.Tests/TestAssemblies/Brutal.Dev.StrongNameSigner.TestAssembly.B.dll b/src/Brutal.Dev.StrongNameSigner.Tests/TestAssemblies/Brutal.Dev.StrongNameSigner.TestAssembly.B.dll index 2334f2a04af74bd0ca882bf4dc979063a6dbec71..eccccc01bcdeb9ac1c23ee50bdfadf500257a4c1 100644 GIT binary patch delta 1271 zcmYk6Urbw79LK-s(tF$9wT0S%W9ybno6v4!*ac7=ZW*BqFqxCx4FwtplxwNdmbDj? zY646R>*Aur--CNH@nzYjW>KOp@nIn*`lvC+V8X+E&=(&}G(M;={?38Uo#uRg=lAFQ z&dEK$UssYV$!oRPor%>+?2P+}S-V6EFxlYSHvX7gyFC_qGYOoLh7&-|v(^>~OaTGb zM-o7k+O!TD7YS|4N3V-^QKMVpxz@Y9M~)$$8g#f8uxS8-M$IQ;?OL))9dr-a}>p+z0RXUGd}Pq|0xe9VIh{3N6f80Z$`nt=}#N087OQRS@$Tr{%% zU&SrOnvj-riUq~Dq>`?`R3u{AkkzUtjuKnI;Y}n{Y(&NyX)UC59D86~`5)na#+n^+m<1;w8l^ zOaq<#EQgp8bTjwDX2$uOce3>=vrDm0aX{Ih#=C5{m1R=$6tcL@rkC*pvx1)$!$PhP zFki!<*aOc>{R?1c$P(oDeK7W;hd%N2(LUV_4ld?r`&}-CnMKzj%iA0oC>6%?mHf=S z^S^ynx9Bk=LK(f4J~Q^wmwJM3=*{$p5v8=(qCfSgyu5E=x>%(NZ;~dBMsv(5SMsGI zR~T)0Qb+7+#dQkU7M*9E7w4U<%YJlb-BP)3Pv@s|#Ztx1XDc*eSoFPNMbpmAVlKyd zPM2n#3eL>D#5-X-*|Ou(CGUm455Il;=4j-vQ=hgU{`^GC{4hM05WWTrLgz&|EY*;N z*5DN(-xsfD##+1YU*Uid+5V*8C*V~@N*8>;(WAbhKJ}^U?JR35%Kka-&pB>@vv%5k zd@geF`OCeh*9X3OFLo?CEVrkRW(M;vedj-^t1DB~yiO@C8vJOddu7(IX~W#B-A%6l zY9<9unY|PW91nFo^gJCNnh3lvuF~IuxHi{7`-3h0dsO`npo9Yd92B`}RNr#O*h9Vr zOtY_wXIRR(`dFf#;2p6Q delta 1290 zcmY+EZD?Cn7{~wTCimv0Y1*uHHeD!5)2&%2nX?y z!qRrF{6b#*DPUFuzN8+{IF?ynvgIUwI*=n9pU$98e!KL?rzg{7&r^I(jvXyl@)XNP z!`XNz;FG~0I|s3y1ado?IhhBoj}UdKQ(qzDR{>z)6e`5maF+NLE)joJbTR4oDy9_g zQ>-YSRs5K}$_#w1_=8gZQaq$}YX(d$tBqlqc3|L~Qr;q}_2o|FUgWPGLr78Cq<>FT z>ZiMw)dpu(w2D;(DHp;rJxN$Grnp;ipW=|>h~lK;y^3?ho3WsLQ}K}%Won8~6C-F4 zd+`#{Plc3Bq6PiL4ysNPJFrfSEACMoRC)>*Ngq>6M)7{CT@nxD8qvmYihWG_!^9_W zjJR(V1v1a0$lUejTcL;EY$>wtt$-}U2|&E-P8rF?ViBX_Blm&*nVRkJ67J4F#0w6R z1*c*m!wVL7&`c$#s%6J4@nn`;wpl6D=@Jj<-Jz{4KT)KSln{ILsQ6I7NxY!-d7s&}-F*fuZai?y+8(WzQol#KAmWZB_XD`G`X&IA3)* z#;fMyf6>+~o7)g2TIErrO+-B*aYc_D*xXOrTv#esY{x9vNb`JkX=&kqlQuPah&wBb z+!jCUF)?TKh`T(;5B~Gr-j_c;{?q8|wO_ySy!icX@V@_eE*su|^*L7*jC#-=WlTZS z$qOBku~)c!6YjkX1EOIJh&#NOd|sx$6)N^MPojBKY+V_2W)YzyyGD1w8hvO}nRI3& z_j%v^7q!C|XV)Jb?z;YtJkfDcK vJ+0eWT~u4&ChiP=#?FXv+q3MkjaS;{HPP-b`l8L7t9@uAwCy@;-0Jxk08SJx diff --git a/src/Brutal.Dev.StrongNameSigner.Tests/TestAssemblies/Brutal.Dev.StrongNameSigner.TestAssembly.B.pdb b/src/Brutal.Dev.StrongNameSigner.Tests/TestAssemblies/Brutal.Dev.StrongNameSigner.TestAssembly.B.pdb new file mode 100644 index 0000000000000000000000000000000000000000..060d6cdd3a98b79e9fcbdfe55bf64fa03dd81913 GIT binary patch literal 11776 zcmeHMUuauZ7(X{l)23b5rPVk$kuWycA9s^ZX+u#<+td+Urduk`8Dvd*+lHly%e~e0 zK?w*O_#k`mKM0BsgYChG4iw#_;26wDr#|^GWC)5lCxUxg{Qd5|XS)kqJF$#3IbZwT z@7(V@=R4n@+;hKkZYrNEm(0?n6&Z<+Y~LP9rJsuQ>#k*Ri!Of~*nMsvopkU4^THMvlPk^53QM-^eMy z#kaThUhT63M_<&y?VIj7y#H7{H9zvrdmEp4C@DKqM)bl@dmpW3dBv)|LLzXx=btY{ z@A}Vsx>HM6h>Ob1MIxZ`zesSJKy)p;ckq1c#nADzSa{}B-zVorpZ?(2U%m?+A9U_J zK010JG2xGe$Z;RW!;O zvz*HemuIcE#W?0XeLQD)~rl8EUl@+1Pys0efD4T$? zTDfjP*|=1e&19*J)kq4shgJe;vD%y3q4=Md4L`@U3Us{Fb78AF_`UXDi+c9Q5wV>S zIhI-40;KLI=TDu;xN@061NRE*BLaM~bg?Z&YcBBu2s*^RBBud1?A+=(tAw|DSgZ6Wt#9?tJkuhTp;)Gmco~ z`7r%}@cSTqn+ZrJSA(Sx0p&5%S^JC@A|&yx(o_@bJ)FKH%cv0cop;rJDCX0+FSr*ns65Qu-NM1#rk4|UatF2-^+GicdOKPe>~klO?}j_c z6@TSNfH{6O*lXAQ9ir>4)neatC)_#$WDUbQVLwnp7>x(*n?DvL&p93To)1e2X}cJ6 zU+VAUuJ-+Y_uEQ+SS0%T<3po6hoEQ2m-% z(dO17j0yXavVN0RFX#4H?z>$xIGqEW)PbOFfYX-JWv7dM|Cz&VQ55HglEulAh}wI2 z5yfFZblMo7Fv{6NDR;Cd^@ptMiGrN*MSz7st;3c zAnuN=LAMC-2Vfj{0`Vp#d=0DsxqsgR#A728K&}boaWB#hWD2|~;WNiH@%dw&$n}Kt e@LhvFi-J0eKu;anqD)Z{P!UiOP!YIy5%?FQ7omv& literal 0 HcmV?d00001 diff --git a/src/Brutal.Dev.StrongNameSigner.sln b/src/Brutal.Dev.StrongNameSigner.sln index 28912c6..a53364e 100644 --- a/src/Brutal.Dev.StrongNameSigner.sln +++ b/src/Brutal.Dev.StrongNameSigner.sln @@ -29,6 +29,7 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Brutal.Dev.StrongNameSigner EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{239CD9FD-F026-419E-9C5D-4AAB4627A614}" ProjectSection(SolutionItems) = preProject + .editorconfig = .editorconfig Brutal.Dev.StrongNameSigner.Setup\EULA.txt = Brutal.Dev.StrongNameSigner.Setup\EULA.txt ..\README.md = ..\README.md Brutal.Dev.StrongNameSigner.Setup\StrongNameSigner.iss = Brutal.Dev.StrongNameSigner.Setup\StrongNameSigner.iss From 47572f52d37f2e64d7664e120a66e3102e3b3c9d Mon Sep 17 00:00:00 2001 From: Peter Palotas Date: Wed, 29 Aug 2018 14:46:16 +0200 Subject: [PATCH 3/3] - Updated a couple of test assemblies with PDB-files. - Added strong references to the Mono.Cecil.Pdb, Mdb and Rocks libraries so that they are among other things properly copied to the test project. - Fixed temporary file handling in SigningHelper, so that it now uses a temporary directory to store the generated files during generation and ensures they are deleted afterwards. --- ...al.Dev.StrongNameSigner.TestAssembly.A.dll | Bin 5120 -> 5120 bytes ...al.Dev.StrongNameSigner.TestAssembly.A.pdb | Bin 11776 -> 11776 bytes ...al.Dev.StrongNameSigner.TestAssembly.B.dll | Bin 5120 -> 5120 bytes ...al.Dev.StrongNameSigner.TestAssembly.B.pdb | Bin 11776 -> 11776 bytes .../Brutal.Dev.StrongNameSigner.csproj | 1 + .../ForceAssemblyReferenceAttribute.cs | 21 + .../Properties/AssemblyInfo.cs | 12 +- .../SigningHelper.cs | 403 ++++++++++++------ 8 files changed, 295 insertions(+), 142 deletions(-) create mode 100644 src/Brutal.Dev.StrongNameSigner/ForceAssemblyReferenceAttribute.cs diff --git a/src/Brutal.Dev.StrongNameSigner.Tests/TestAssemblies/Brutal.Dev.StrongNameSigner.TestAssembly.A.dll b/src/Brutal.Dev.StrongNameSigner.Tests/TestAssemblies/Brutal.Dev.StrongNameSigner.TestAssembly.A.dll index 2c98f2b25af05ff11877bc1d3150b20c18845ead..48ef70489a4a65fd841f429f3b1ab7f471ac5a9a 100644 GIT binary patch delta 85 zcmZqBXwaC@!NgU&vFjR}!1|ll7H?la-D8W~+KE#Rr0Q-iU^igY;i_$mW&i>vAeLcd mVA!Ds#2gF^LBTG;-vfdp-CHmEZdE+@re{M*=;kvVCfoo+{2p}x delta 85 zcmZqBXwaC@!L%uAW7jn{0Z-}7#)?lh-W_uu`>jj~!TF~HHlN`z;RXN=-yF>V diff --git a/src/Brutal.Dev.StrongNameSigner.Tests/TestAssemblies/Brutal.Dev.StrongNameSigner.TestAssembly.A.pdb b/src/Brutal.Dev.StrongNameSigner.Tests/TestAssemblies/Brutal.Dev.StrongNameSigner.TestAssembly.A.pdb index c2dc30224b58b7ce93b80e0f41dbd2980e67cd8f..3e45de6c5916b1ea8ffcdad6d14d33ac26d011c5 100644 GIT binary patch delta 78 zcmZpOX^7dt!X?2~+ZN5pz`*c5AUM*!^`h@q#dB|ZHk5>Jmf?EC&AQdOsx6w4fq`Moq{WppJY?M3Z)Hjd&OaTnS%&KkH|v@0LE6QedBwi- K;!&vbgcAT~rX3dm diff --git a/src/Brutal.Dev.StrongNameSigner.Tests/TestAssemblies/Brutal.Dev.StrongNameSigner.TestAssembly.B.dll b/src/Brutal.Dev.StrongNameSigner.Tests/TestAssemblies/Brutal.Dev.StrongNameSigner.TestAssembly.B.dll index eccccc01bcdeb9ac1c23ee50bdfadf500257a4c1..61490a29425ea162548aa8c5e97c2e23a5301759 100644 GIT binary patch delta 85 zcmZqBXwaC@!NgU&u}g_v;66v!l=L(;uf-qD|3vGE7;WCeK7~<-tF|qg0SK6YScZ{- mAwwOAIT;v&f?a~&SPFZ)F7xu+b5iN!swG#oS`%*ntI6zmdgmLXgi(Qe_p+4=BvC0iNK%{81YTmZFm8QlN? diff --git a/src/Brutal.Dev.StrongNameSigner.Tests/TestAssemblies/Brutal.Dev.StrongNameSigner.TestAssembly.B.pdb b/src/Brutal.Dev.StrongNameSigner.Tests/TestAssemblies/Brutal.Dev.StrongNameSigner.TestAssembly.B.pdb index 060d6cdd3a98b79e9fcbdfe55bf64fa03dd81913..c5560f37bf2cf959888e86685f661d640b7f8ecb 100644 GIT binary patch delta 78 zcmZpOX^7dt!X?2~+ZN5pz`*dvQrO#dnU~+5lS&^~ExBU8S%&KkH>=CG=Zo7n^NM}v K#iLN=2`2z$bRGc! delta 78 zcmZpOX^7dt!X>djsx6w4fq}s+L%1-a-NJXX^Wo`AwlbcZWw_pOvrZL0sad?4SL{14 J9)&7TI04;P8BG8H diff --git a/src/Brutal.Dev.StrongNameSigner/Brutal.Dev.StrongNameSigner.csproj b/src/Brutal.Dev.StrongNameSigner/Brutal.Dev.StrongNameSigner.csproj index 237861c..8c69185 100644 --- a/src/Brutal.Dev.StrongNameSigner/Brutal.Dev.StrongNameSigner.csproj +++ b/src/Brutal.Dev.StrongNameSigner/Brutal.Dev.StrongNameSigner.csproj @@ -72,6 +72,7 @@ + diff --git a/src/Brutal.Dev.StrongNameSigner/ForceAssemblyReferenceAttribute.cs b/src/Brutal.Dev.StrongNameSigner/ForceAssemblyReferenceAttribute.cs new file mode 100644 index 0000000..d18350b --- /dev/null +++ b/src/Brutal.Dev.StrongNameSigner/ForceAssemblyReferenceAttribute.cs @@ -0,0 +1,21 @@ +using Brutal.Dev.StrongNameSigner; +using System; +using System.Linq; + +namespace Brutal.Dev.StrongNameSigner +{ + /// + /// Attribute used to force an assembly reference to a specific assembly to be actually referenced and copied + /// locally. + /// See https://stackoverflow.com/questions/15816769/dependent-dll-is-not-getting-copied-to-the-build-output-folder-in-visual-studio + /// + [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] + internal sealed class ForceAssemblyReferenceAttribute : Attribute + { + public ForceAssemblyReferenceAttribute(Type forcedType) + { + Action noop = _ => { }; + noop(forcedType); + } + } +} diff --git a/src/Brutal.Dev.StrongNameSigner/Properties/AssemblyInfo.cs b/src/Brutal.Dev.StrongNameSigner/Properties/AssemblyInfo.cs index ac3619c..8f111ba 100644 --- a/src/Brutal.Dev.StrongNameSigner/Properties/AssemblyInfo.cs +++ b/src/Brutal.Dev.StrongNameSigner/Properties/AssemblyInfo.cs @@ -1,4 +1,5 @@ -using System; +using Brutal.Dev.StrongNameSigner; +using System; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -36,3 +37,12 @@ // [assembly: AssemblyVersion("1.0.*")] [assembly: AssemblyVersion("2.2.0.0")] [assembly: AssemblyFileVersion("2.2.0.0")] + + +// These assemblies are used by Cecil, and reading assemblies with symbols without these DLL's present +// will cause an error ("No Symbols Found"). So to ensure that these are actually referenced by +// StrongNameSigner and copied along to the output directory as well as the UnitTests when running +// them, we use this "hack". +[assembly: ForceAssemblyReference(typeof(Mono.Cecil.Pdb.NativePdbReader))] +[assembly: ForceAssemblyReference(typeof(Mono.Cecil.Mdb.MdbReader))] +[assembly: ForceAssemblyReference(typeof(Mono.Cecil.Rocks.TypeDefinitionRocks))] diff --git a/src/Brutal.Dev.StrongNameSigner/SigningHelper.cs b/src/Brutal.Dev.StrongNameSigner/SigningHelper.cs index 1a66d36..eda9bcf 100644 --- a/src/Brutal.Dev.StrongNameSigner/SigningHelper.cs +++ b/src/Brutal.Dev.StrongNameSigner/SigningHelper.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; @@ -122,95 +123,42 @@ public static AssemblyInfo SignAssembly(string assemblyPath, string keyPath, str } string outputFile = Path.Combine(Path.GetFullPath(outputPath), Path.GetFileName(assemblyPath)); - bool writeSymbols = File.Exists(Path.ChangeExtension(assemblyPath, ".pdb")); + using (OutputFileManager outputFileMgr = new OutputFileManager(assemblyPath, outputFile)) + { + // Get the assembly info and go from there. - // Get the assembly info and go from there. - AssemblyInfo info = GetAssemblyInfo(assemblyPath); + AssemblyInfo info = GetAssemblyInfo(assemblyPath); - // Don't sign assemblies with a strong-name signature. - if (info.IsSigned) - { - if (!outputFile.Equals(Path.GetFullPath(assemblyPath), StringComparison.OrdinalIgnoreCase)) + // Don't sign assemblies with a strong-name signature. + if (info.IsSigned) { - if (File.Exists(outputFile)) + // If the target directory is different from the input... + if (!outputFileMgr.IsInPlaceReplace) { - File.SetAttributes(outputFile, FileAttributes.Normal); + // ...just copy the source file to the destination. + outputFileMgr.CopySourceToFinalOutput(); } - File.Copy(assemblyPath, outputFile, true); - - if (writeSymbols) - { - var newPdbFile = Path.ChangeExtension(outputFile, ".pdb"); - if (File.Exists(newPdbFile)) - { - File.SetAttributes(newPdbFile, FileAttributes.Normal); - } - - File.Copy(Path.ChangeExtension(assemblyPath, ".pdb"), newPdbFile, true); - } + return GetAssemblyInfo(outputFile); } - return GetAssemblyInfo(outputFile); - } - if (outputFile.Equals(Path.GetFullPath(assemblyPath), StringComparison.OrdinalIgnoreCase)) - { - // Make a backup before overwriting. - var backupFile = outputFile + ".unsigned"; - if (File.Exists(backupFile)) + if (outputFileMgr.IsInPlaceReplace) { - File.SetAttributes(backupFile, FileAttributes.Normal); + outputFileMgr.CreateBackup(); } - File.Copy(outputFile, backupFile, true); - } - - try - { - if (File.Exists(outputFile)) - { - File.SetAttributes(outputFile, FileAttributes.Normal); - } - - var tempOutputFile = outputFile; using (var ad = AssemblyDefinition.ReadAssembly(assemblyPath, GetReadParameters(assemblyPath, probingPaths))) { - if (assemblyPath.Equals(outputFile, StringComparison.OrdinalIgnoreCase)) - { - // Can't overwrite with a lock in place, create a copy and rename after disposing. - tempOutputFile = Path.GetTempFileName(); - } - - ad.Write(tempOutputFile, new WriterParameters() { StrongNameKeyPair = GetStrongNameKeyPair(keyPath, keyFilePassword), WriteSymbols = writeSymbols }); - } - - if (tempOutputFile != outputFile && File.Exists(tempOutputFile)) - { - File.SetAttributes(tempOutputFile, FileAttributes.Normal); - File.Copy(tempOutputFile, outputFile, true); - File.Delete(tempOutputFile); + ad.Write(outputFileMgr.IntermediateAssemblyPath, new WriterParameters() { StrongNameKeyPair = GetStrongNameKeyPair(keyPath, keyFilePassword), WriteSymbols = outputFileMgr.HasSymbols }); } AssemblyInfoCache.TryRemove(assemblyPath, out KeyValuePair _); - } - catch (Exception) - { - // Restore the backup if something goes wrong. - if (outputFile.Equals(Path.GetFullPath(assemblyPath), StringComparison.OrdinalIgnoreCase)) - { - if (File.Exists(outputFile)) - { - File.SetAttributes(outputFile, FileAttributes.Normal); - } - File.Copy(outputFile + ".unsigned", outputFile, true); - } + outputFileMgr.Commit(); - throw; + return GetAssemblyInfo(outputFile); } - - return GetAssemblyInfo(outputFile); } /// @@ -333,68 +281,49 @@ public static bool FixAssemblyReference(string assemblyPath, string referenceAss } bool fixApplied = false; - var tempOutputFileA = Path.GetTempFileName(); - var tempOutputFileB = Path.GetTempFileName(); - using (var a = AssemblyDefinition.ReadAssembly(assemblyPath, GetReadParameters(assemblyPath, probingPaths))) - using (var b = AssemblyDefinition.ReadAssembly(referenceAssemblyPath, GetReadParameters(referenceAssemblyPath, probingPaths))) + using (var aMgr = new OutputFileManager(assemblyPath, assemblyPath)) + using (var bMgr = new OutputFileManager(referenceAssemblyPath, referenceAssemblyPath)) { - var assemblyReference = a.MainModule.AssemblyReferences.FirstOrDefault(r => r.Name.Equals(b.Name.Name, StringComparison.OrdinalIgnoreCase)); - - // Found a matching reference, let's set the public key token. - if (assemblyReference != null && BitConverter.ToString(assemblyReference.PublicKeyToken) != BitConverter.ToString(b.Name.PublicKeyToken)) + using (var a = AssemblyDefinition.ReadAssembly(assemblyPath, GetReadParameters(assemblyPath, probingPaths))) + using (var b = AssemblyDefinition.ReadAssembly(referenceAssemblyPath, GetReadParameters(referenceAssemblyPath, probingPaths))) { - assemblyReference.PublicKeyToken = b.Name.PublicKeyToken ?? new byte[0]; - assemblyReference.Version = b.Name.Version; + var assemblyReference = a.MainModule.AssemblyReferences.FirstOrDefault(r => r.Name.Equals(b.Name.Name, StringComparison.OrdinalIgnoreCase)); - if (File.Exists(assemblyPath)) + // Found a matching reference, let's set the public key token. + if (assemblyReference != null && BitConverter.ToString(assemblyReference.PublicKeyToken) != BitConverter.ToString(b.Name.PublicKeyToken)) { - File.SetAttributes(assemblyPath, FileAttributes.Normal); - } + assemblyReference.PublicKeyToken = b.Name.PublicKeyToken ?? new byte[0]; + assemblyReference.Version = b.Name.Version; - a.Write(tempOutputFileA, new WriterParameters { StrongNameKeyPair = GetStrongNameKeyPair(keyPath, keyFilePassword), WriteSymbols = File.Exists(Path.ChangeExtension(assemblyPath, ".pdb")) }); + a.Write(aMgr.IntermediateAssemblyPath, new WriterParameters { StrongNameKeyPair = GetStrongNameKeyPair(keyPath, keyFilePassword), WriteSymbols = File.Exists(Path.ChangeExtension(assemblyPath, ".pdb")) }); - AssemblyInfoCache.TryRemove(assemblyPath, out KeyValuePair _); - - fixApplied = true; - } + AssemblyInfoCache.TryRemove(assemblyPath, out KeyValuePair _); - var friendReference = b.CustomAttributes.SingleOrDefault(attr => attr.AttributeType.FullName == typeof(InternalsVisibleToAttribute).FullName && - attr.ConstructorArguments[0].Value.ToString() == a.Name.Name); + fixApplied = true; + } - if (friendReference != null && a.Name.HasPublicKey) - { - // Add the public key to the attribute. - var typeRef = friendReference.ConstructorArguments[0].Type; - friendReference.ConstructorArguments.Clear(); - friendReference.ConstructorArguments.Add(new CustomAttributeArgument(typeRef, a.Name.Name + ", PublicKey=" + BitConverter.ToString(a.Name.PublicKey).Replace("-", string.Empty))); + var friendReference = b.CustomAttributes.SingleOrDefault(attr => attr.AttributeType.FullName == typeof(InternalsVisibleToAttribute).FullName && + attr.ConstructorArguments[0].Value.ToString() == a.Name.Name); - // Save and resign. - if (File.Exists(referenceAssemblyPath)) + if (friendReference != null && a.Name.HasPublicKey) { - File.SetAttributes(referenceAssemblyPath, FileAttributes.Normal); - } + // Add the public key to the attribute. + var typeRef = friendReference.ConstructorArguments[0].Type; + friendReference.ConstructorArguments.Clear(); + friendReference.ConstructorArguments.Add(new CustomAttributeArgument(typeRef, a.Name.Name + ", PublicKey=" + BitConverter.ToString(a.Name.PublicKey).Replace("-", string.Empty))); - b.Write(tempOutputFileB, new WriterParameters { StrongNameKeyPair = GetStrongNameKeyPair(keyPath, keyFilePassword), WriteSymbols = File.Exists(Path.ChangeExtension(referenceAssemblyPath, ".pdb")) }); + // Save and resign. + b.Write(bMgr.IntermediateAssemblyPath, new WriterParameters { StrongNameKeyPair = GetStrongNameKeyPair(keyPath, keyFilePassword), WriteSymbols = File.Exists(Path.ChangeExtension(referenceAssemblyPath, ".pdb")) }); - AssemblyInfoCache.TryRemove(assemblyPath, out KeyValuePair _); + AssemblyInfoCache.TryRemove(assemblyPath, out KeyValuePair _); - fixApplied = true; + fixApplied = true; + } } - } - if (File.Exists(tempOutputFileA)) - { - File.SetAttributes(tempOutputFileA, FileAttributes.Normal); - File.Copy(tempOutputFileA, assemblyPath, true); - File.Delete(tempOutputFileA); - } - - if (File.Exists(tempOutputFileB)) - { - File.SetAttributes(tempOutputFileB, FileAttributes.Normal); - File.Copy(tempOutputFileB, referenceAssemblyPath, true); - File.Delete(tempOutputFileB); + aMgr.Commit(); + bMgr.Commit(); } return fixApplied; @@ -443,41 +372,31 @@ public static bool RemoveInvalidFriendAssemblies(string assemblyPath, string key } bool fixApplied = false; - var tempOutputFile = Path.GetTempFileName(); - - using (var a = AssemblyDefinition.ReadAssembly(assemblyPath, GetReadParameters(assemblyPath, probingPaths))) + using (var outFileMgr = new OutputFileManager(assemblyPath, assemblyPath)) { - var ivtAttributes = a.CustomAttributes.Where(attr => attr.AttributeType.FullName == typeof(InternalsVisibleToAttribute).FullName).ToList(); - - foreach (var friendReference in ivtAttributes) + using (var a = AssemblyDefinition.ReadAssembly(assemblyPath, GetReadParameters(assemblyPath, probingPaths))) { - // Find any without a public key defined. - if (friendReference.HasConstructorArguments && friendReference.ConstructorArguments.Any(ca => ca.Value != null && ca.Value.ToString().IndexOf("PublicKey=", StringComparison.Ordinal) == -1)) - { - a.CustomAttributes.Remove(friendReference); - fixApplied = true; - } - } + var ivtAttributes = a.CustomAttributes.Where(attr => attr.AttributeType.FullName == typeof(InternalsVisibleToAttribute).FullName).ToList(); - if (fixApplied) - { - // Save and resign. - if (File.Exists(assemblyPath)) + foreach (var friendReference in ivtAttributes) { - File.SetAttributes(assemblyPath, FileAttributes.Normal); + // Find any without a public key defined. + if (friendReference.HasConstructorArguments && friendReference.ConstructorArguments.Any(ca => ca.Value != null && ca.Value.ToString().IndexOf("PublicKey=", StringComparison.Ordinal) == -1)) + { + a.CustomAttributes.Remove(friendReference); + fixApplied = true; + } } - a.Write(tempOutputFile, new WriterParameters { StrongNameKeyPair = GetStrongNameKeyPair(keyPath, keyFilePassword), WriteSymbols = File.Exists(Path.ChangeExtension(assemblyPath, ".pdb")) }); + if (fixApplied) + { + a.Write(outFileMgr.IntermediateAssemblyPath, new WriterParameters { StrongNameKeyPair = GetStrongNameKeyPair(keyPath, keyFilePassword), WriteSymbols = File.Exists(Path.ChangeExtension(assemblyPath, ".pdb")) }); - AssemblyInfoCache.TryRemove(assemblyPath, out KeyValuePair _); + AssemblyInfoCache.TryRemove(assemblyPath, out KeyValuePair _); + } } - } - if (File.Exists(tempOutputFile)) - { - File.SetAttributes(tempOutputFile, FileAttributes.Normal); - File.Copy(tempOutputFile, assemblyPath, true); - File.Delete(tempOutputFile); + outFileMgr.Commit(); } return fixApplied; @@ -591,5 +510,207 @@ private static string GetFileMD5Hash(string filePath) return sb.ToString(); } + + /// + /// Utility class that assists in the handling of temporary files during assembly signing. It will create + /// a temporary directory to hold generated files during the signing process, and will move these to their + /// final location upon calling . It also ensures that all temporary/intermediate files + /// are deleted upon disposing the instance. + /// + /// + private sealed class OutputFileManager : IDisposable + { + private string tempDir; + + public OutputFileManager(string sourceAssemblyPath, string targetAssemblyPath) + { + SourceAssemblyPath = Path.GetFullPath(sourceAssemblyPath); + TargetAssemblyPath = Path.GetFullPath(targetAssemblyPath); + IsInPlaceReplace = String.Equals(SourceAssemblyPath, TargetAssemblyPath, StringComparison.Ordinal); + + if (IsInPlaceReplace) + { + tempDir = Path.Combine(Path.GetDirectoryName(sourceAssemblyPath), $"StrongNamerTemp.{Process.GetCurrentProcess().Id}.{Path.GetRandomFileName()}"); + Directory.CreateDirectory(tempDir); + IntermediateAssemblyPath = Path.Combine(tempDir, Path.GetFileName(sourceAssemblyPath)); + } + else + { + IntermediateAssemblyPath = TargetAssemblyPath; + } + + HasSymbols = File.Exists(Path.ChangeExtension(SourceAssemblyPath, ".pdb")); + } + + ~OutputFileManager() + { + Dispose(); + } + + #region Properties + + private bool UseTemporaryDirectory => tempDir != null; + + /// + /// Gets a value indicating whether the source assembly has a matching pdb file. + /// + public bool HasSymbols { get; } + + /// + /// Indicates whether the SourceAssembyPath and the TargetAssemblyPath are equal. + /// + public bool IsInPlaceReplace { get; } + + /// + /// Gets the path of the source assembly. + /// + public string SourceAssemblyPath { get; } + + /// + /// Gets the path to the .pdb file of the source assembly. (Does not check for existance of the file) + /// + public string SourcePdbPath => Path.ChangeExtension(SourceAssemblyPath, ".pdb"); + + /// + /// Gets the intermediate path to which the new assembly should be written. This may be the same as , + /// if it is different from . (This property is never equal to ). + /// + public string IntermediateAssemblyPath { get; } + + /// + /// Gets the intermediate path to which the new .pdb should be written. This may be the same as , + /// if it is different from . + /// + public string IntermediatePdbPath => Path.ChangeExtension(IntermediateAssemblyPath, ".pdb"); + + /// + /// Gets the path to where the final output assembly should reside. This may be the same as . + /// + public string TargetAssemblyPath { get; } + + /// + /// Gets the path to the .pdb file of the target assembly. (Does not check for existance of the file) + /// + public string TargetPdbPath => Path.ChangeExtension(TargetAssemblyPath, ".pdb"); + + /// + /// Gets the path to where a backup of the source assembly should be saved. + /// + public string BackupAssemblyPath => SourceAssemblyPath + ".unsigned"; + + /// + /// Gets the path to where a backup of the source .pdb file should be saved. + /// + public string BackupPdbPath => SourcePdbPath + ".unsigned"; + + /// + /// This property will be set to true after has been called. + /// + public bool HasBackup { get; private set; } + + #endregion + + /// + /// Creates a backup of the input files by simply copying them to the and . + /// + public void CreateBackup() + { + CopyFile(SourceAssemblyPath, BackupAssemblyPath, false); + if (HasSymbols) + CopyFile(SourcePdbPath, BackupPdbPath, false); + + HasBackup = true; + } + + /// + /// Directly copies and to + /// and (if the source files exists). + /// + public void CopySourceToFinalOutput() + { + CopyFile(SourceAssemblyPath, TargetAssemblyPath, false); + if (HasSymbols) + CopyFile(SourcePdbPath, TargetPdbPath, false); + } + + /// + /// Moves the intermediate files to the target locations if a temporary directory was used during generation. + /// Otherwise this method does nothing. + /// + public void Commit() + { + if (UseTemporaryDirectory) + { + try + { + // Only move files if the target assembly to move actually was created. + CopyFile(IntermediateAssemblyPath, TargetAssemblyPath, true); + if (HasSymbols) + { + CopyFile(IntermediatePdbPath, TargetPdbPath, true); + } + } + catch (Exception) + { + if (HasBackup) + { + // Restore backup + CopyFile(BackupAssemblyPath, SourceAssemblyPath, true); + CopyFile(BackupPdbPath, SourcePdbPath, true); + } + throw; + } + } + else + { + // Nothing to commit if we didn't use a temporary directory. + } + } + + public void Dispose() + { + GC.SuppressFinalize(this); + if (tempDir != null) + { + try + { + Directory.Delete(tempDir, true); + tempDir = null; + } + catch + { + // Ignore errors when attempting to clean up temporary directory. + } + } + } + + /// + /// Copies or moves a single file if it exists. It will always overwrite the target if it exists (provided that + /// the source file exists). + /// + /// The source file to copy/move if it exists. + /// The target file to write to. + /// if set to the file is moved, otherwise it is copied. + private static void CopyFile(string source, string target, bool move) + { + if (File.Exists(source)) + { + if (File.Exists(target)) + { + File.SetAttributes(target, FileAttributes.Normal); + File.Delete(target); + } + + if (move) + { + File.Move(source, target); + } + else + { + File.Copy(source, target); + } + } + } + } } }