1 /* 2 KeePass Password Safe - The Open-Source Password Manager 3 Copyright (C) 2003-2021 Dominik Reichl <dominik.reichl@t-online.de> 4 5 This program is free software; you can redistribute it and/or modify 6 it under the terms of the GNU General Public License as published by 7 the Free Software Foundation; either version 2 of the License, or 8 (at your option) any later version. 9 10 This program is distributed in the hope that it will be useful, 11 but WITHOUT ANY WARRANTY; without even the implied warranty of 12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 GNU General Public License for more details. 14 15 You should have received a copy of the GNU General Public License 16 along with this program; if not, write to the Free Software 17 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 18 */ 19 20 using System; 21 using System.Collections.Generic; 22 using System.ComponentModel; 23 using System.Diagnostics; 24 using System.Reflection; 25 using System.Runtime.InteropServices; 26 using System.Text; 27 using System.Text.RegularExpressions; 28 29 #if !KeePassUAP 30 using System.IO; 31 using System.Threading; 32 using System.Windows.Forms; 33 #endif 34 35 using KeePassLib.Resources; 36 using KeePassLib.Utility; 37 38 namespace KeePassLib.Native 39 { 40 /// <summary> 41 /// Interface to native library (library containing fast versions of 42 /// several cryptographic functions). 43 /// </summary> 44 public static class NativeLib 45 { 46 private static bool m_bAllowNative = true; 47 48 /// <summary> 49 /// If this property is set to <c>true</c>, the native library is used. 50 /// If it is <c>false</c>, all calls to functions in this class will fail. 51 /// </summary> 52 public static bool AllowNative 53 { 54 get { return m_bAllowNative; } 55 set { m_bAllowNative = value; } 56 } 57 58 private static ulong? m_ouMonoVersion = null; 59 public static ulong MonoVersion 60 { 61 get 62 { 63 if(m_ouMonoVersion.HasValue) return m_ouMonoVersion.Value; 64 65 ulong uVersion = 0; 66 try 67 { 68 Type t = Type.GetType("Mono.Runtime"); 69 if(t != null) 70 { 71 MethodInfo mi = t.GetMethod("GetDisplayName", 72 BindingFlags.NonPublic | BindingFlags.Static); 73 if(mi != null) 74 { 75 string strName = (mi.Invoke(null, null) as string); 76 if(!string.IsNullOrEmpty(strName)) 77 { 78 Match m = Regex.Match(strName, "\\d+(\\.\\d+)+"); 79 if(m.Success) 80 uVersion = StrUtil.ParseVersion(m.Value); 81 else { Debug.Assert(false); } 82 } 83 else { Debug.Assert(false); } 84 } 85 else { Debug.Assert(false); } 86 } 87 } 88 catch(Exception) { Debug.Assert(false); } 89 90 m_ouMonoVersion = uVersion; 91 return uVersion; 92 } 93 } 94 95 /// <summary> 96 /// Determine if the native library is installed. 97 /// </summary> 98 /// <returns>Returns <c>true</c>, if the native library is installed.</returns> IsLibraryInstalled()99 public static bool IsLibraryInstalled() 100 { 101 byte[] pDummy0 = new byte[32]; 102 byte[] pDummy1 = new byte[32]; 103 104 // Save the native state 105 bool bCachedNativeState = m_bAllowNative; 106 107 // Temporarily allow native functions and try to load the library 108 m_bAllowNative = true; 109 bool bResult = TransformKey256(pDummy0, pDummy1, 16); 110 111 // Pop native state and return result 112 m_bAllowNative = bCachedNativeState; 113 return bResult; 114 } 115 116 private static bool? m_bIsUnix = null; IsUnix()117 public static bool IsUnix() 118 { 119 if(m_bIsUnix.HasValue) return m_bIsUnix.Value; 120 121 PlatformID p = GetPlatformID(); 122 123 // Mono defines Unix as 128 in early .NET versions 124 #if !KeePassLibSD 125 m_bIsUnix = ((p == PlatformID.Unix) || (p == PlatformID.MacOSX) || 126 ((int)p == 128)); 127 #else 128 m_bIsUnix = (((int)p == 4) || ((int)p == 6) || ((int)p == 128)); 129 #endif 130 return m_bIsUnix.Value; 131 } 132 133 private static PlatformID? m_platID = null; GetPlatformID()134 public static PlatformID GetPlatformID() 135 { 136 if(m_platID.HasValue) return m_platID.Value; 137 138 #if KeePassUAP 139 m_platID = EnvironmentExt.OSVersion.Platform; 140 #else 141 m_platID = Environment.OSVersion.Platform; 142 #endif 143 144 #if (!KeePassLibSD && !KeePassUAP) 145 // Mono returns PlatformID.Unix on Mac OS X, workaround this 146 if(m_platID.Value == PlatformID.Unix) 147 { 148 if((RunConsoleApp("uname", null) ?? string.Empty).Trim().Equals( 149 "Darwin", StrUtil.CaseIgnoreCmp)) 150 m_platID = PlatformID.MacOSX; 151 } 152 #endif 153 154 return m_platID.Value; 155 } 156 157 private static DesktopType? m_tDesktop = null; GetDesktopType()158 public static DesktopType GetDesktopType() 159 { 160 if(!m_tDesktop.HasValue) 161 { 162 DesktopType t = DesktopType.None; 163 if(!IsUnix()) t = DesktopType.Windows; 164 else 165 { 166 try 167 { 168 string strXdg = (Environment.GetEnvironmentVariable( 169 "XDG_CURRENT_DESKTOP") ?? string.Empty).Trim(); 170 string strGdm = (Environment.GetEnvironmentVariable( 171 "GDMSESSION") ?? string.Empty).Trim(); 172 StringComparison sc = StrUtil.CaseIgnoreCmp; 173 174 if(strXdg.Equals("Unity", sc)) 175 t = DesktopType.Unity; 176 else if(strXdg.Equals("LXDE", sc)) 177 t = DesktopType.Lxde; 178 else if(strXdg.Equals("XFCE", sc)) 179 t = DesktopType.Xfce; 180 else if(strXdg.Equals("MATE", sc)) 181 t = DesktopType.Mate; 182 else if(strXdg.Equals("X-Cinnamon", sc)) // Mint 18.3 183 t = DesktopType.Cinnamon; 184 else if(strXdg.Equals("Pantheon", sc)) // Elementary OS 185 t = DesktopType.Pantheon; 186 else if(strXdg.Equals("KDE", sc) || // Mint 16, Kubuntu 17.10 187 strGdm.Equals("kde-plasma", sc)) // Ubuntu 12.04 188 t = DesktopType.Kde; 189 else if(strXdg.Equals("GNOME", sc)) 190 { 191 if(strGdm.Equals("cinnamon", sc)) // Mint 13 192 t = DesktopType.Cinnamon; 193 else t = DesktopType.Gnome; // Fedora 27 194 } 195 else if(strXdg.Equals("ubuntu:GNOME", sc)) 196 t = DesktopType.Gnome; 197 } 198 catch(Exception) { Debug.Assert(false); } 199 } 200 201 m_tDesktop = t; 202 } 203 204 return m_tDesktop.Value; 205 } 206 207 private static bool? g_obWayland = null; IsWayland()208 internal static bool IsWayland() 209 { 210 if(!g_obWayland.HasValue) 211 { 212 bool b = false; 213 try 214 { 215 // https://www.freedesktop.org/software/systemd/man/pam_systemd.html 216 b = ((Environment.GetEnvironmentVariable("XDG_SESSION_TYPE") ?? 217 string.Empty).Trim().Equals("wayland", StrUtil.CaseIgnoreCmp)); 218 } 219 catch(Exception) { Debug.Assert(false); } 220 221 g_obWayland = b; 222 } 223 224 return g_obWayland.Value; 225 } 226 227 #if (!KeePassLibSD && !KeePassUAP) RunConsoleApp(string strAppPath, string strParams)228 public static string RunConsoleApp(string strAppPath, string strParams) 229 { 230 return RunConsoleApp(strAppPath, strParams, null); 231 } 232 RunConsoleApp(string strAppPath, string strParams, string strStdInput)233 public static string RunConsoleApp(string strAppPath, string strParams, 234 string strStdInput) 235 { 236 return RunConsoleApp(strAppPath, strParams, strStdInput, 237 (AppRunFlags.GetStdOutput | AppRunFlags.WaitForExit)); 238 } 239 RunProcessDelegate()240 private delegate string RunProcessDelegate(); 241 RunConsoleApp(string strAppPath, string strParams, string strStdInput, AppRunFlags f)242 public static string RunConsoleApp(string strAppPath, string strParams, 243 string strStdInput, AppRunFlags f) 244 { 245 if(strAppPath == null) throw new ArgumentNullException("strAppPath"); 246 if(strAppPath.Length == 0) throw new ArgumentException("strAppPath"); 247 248 bool bStdOut = ((f & AppRunFlags.GetStdOutput) != AppRunFlags.None); 249 250 RunProcessDelegate fnRun = delegate() 251 { 252 Process pToDispose = null; 253 try 254 { 255 ProcessStartInfo psi = new ProcessStartInfo(); 256 257 psi.FileName = strAppPath; 258 if(!string.IsNullOrEmpty(strParams)) psi.Arguments = strParams; 259 260 psi.CreateNoWindow = true; 261 psi.WindowStyle = ProcessWindowStyle.Hidden; 262 psi.UseShellExecute = false; 263 264 psi.RedirectStandardOutput = bStdOut; 265 if(strStdInput != null) psi.RedirectStandardInput = true; 266 267 Process p = StartProcessEx(psi); 268 pToDispose = p; 269 270 if(strStdInput != null) 271 { 272 EnsureNoBom(p.StandardInput); 273 274 p.StandardInput.Write(strStdInput); 275 p.StandardInput.Close(); 276 } 277 278 string strOutput = string.Empty; 279 if(bStdOut) strOutput = p.StandardOutput.ReadToEnd(); 280 281 if((f & AppRunFlags.WaitForExit) != AppRunFlags.None) 282 p.WaitForExit(); 283 else if((f & AppRunFlags.GCKeepAlive) != AppRunFlags.None) 284 { 285 pToDispose = null; // Thread disposes it 286 287 Thread th = new Thread(delegate() 288 { 289 try { p.WaitForExit(); p.Dispose(); } 290 catch(Exception) { Debug.Assert(false); } 291 }); 292 th.Start(); 293 } 294 295 return strOutput; 296 } 297 #if DEBUG 298 catch(ThreadAbortException) { } 299 catch(Win32Exception exW) 300 { 301 Debug.Assert((strAppPath == ClipboardU.XSel) && 302 (exW.NativeErrorCode == 2)); // XSel not found 303 } 304 catch(Exception) { Debug.Assert(false); } 305 #else 306 catch(Exception) { } 307 #endif 308 finally 309 { 310 try { if(pToDispose != null) pToDispose.Dispose(); } 311 catch(Exception) { Debug.Assert(false); } 312 } 313 314 return null; 315 }; 316 317 if((f & AppRunFlags.DoEvents) != AppRunFlags.None) 318 { 319 List<Form> lDisabledForms = new List<Form>(); 320 if((f & AppRunFlags.DisableForms) != AppRunFlags.None) 321 { 322 foreach(Form form in Application.OpenForms) 323 { 324 if(!form.Enabled) continue; 325 326 lDisabledForms.Add(form); 327 form.Enabled = false; 328 } 329 } 330 331 IAsyncResult ar = fnRun.BeginInvoke(null, null); 332 333 while(!ar.AsyncWaitHandle.WaitOne(0)) 334 { 335 Application.DoEvents(); 336 Thread.Sleep(2); 337 } 338 339 string strRet = fnRun.EndInvoke(ar); 340 341 for(int i = lDisabledForms.Count - 1; i >= 0; --i) 342 lDisabledForms[i].Enabled = true; 343 344 return strRet; 345 } 346 347 return fnRun(); 348 } 349 EnsureNoBom(StreamWriter sw)350 private static void EnsureNoBom(StreamWriter sw) 351 { 352 if(sw == null) { Debug.Assert(false); return; } 353 if(!MonoWorkarounds.IsRequired(1219)) return; 354 355 try 356 { 357 Encoding enc = sw.Encoding; 358 if(enc == null) { Debug.Assert(false); return; } 359 byte[] pbBom = enc.GetPreamble(); 360 if((pbBom == null) || (pbBom.Length == 0)) return; 361 362 // For Mono >= 4.0 (using Microsoft's reference source) 363 try 364 { 365 FieldInfo fi = typeof(StreamWriter).GetField("haveWrittenPreamble", 366 BindingFlags.Instance | BindingFlags.NonPublic); 367 if(fi != null) 368 { 369 fi.SetValue(sw, true); 370 return; 371 } 372 } 373 catch(Exception) { Debug.Assert(false); } 374 375 // For Mono < 4.0 376 FieldInfo fiPD = typeof(StreamWriter).GetField("preamble_done", 377 BindingFlags.Instance | BindingFlags.NonPublic); 378 if(fiPD != null) fiPD.SetValue(sw, true); 379 else { Debug.Assert(false); } 380 } 381 catch(Exception) { Debug.Assert(false); } 382 } 383 #endif 384 385 /// <summary> 386 /// Transform a key. 387 /// </summary> 388 /// <param name="pBuf256">Source and destination buffer.</param> 389 /// <param name="pKey256">Key to use in the transformation.</param> 390 /// <param name="uRounds">Number of transformation rounds.</param> 391 /// <returns>Returns <c>true</c>, if the key was transformed successfully.</returns> TransformKey256(byte[] pBuf256, byte[] pKey256, ulong uRounds)392 public static bool TransformKey256(byte[] pBuf256, byte[] pKey256, 393 ulong uRounds) 394 { 395 #if KeePassUAP 396 return false; 397 #else 398 if(!m_bAllowNative) return false; 399 400 KeyValuePair<IntPtr, IntPtr> kvp = PrepareArrays256(pBuf256, pKey256); 401 bool bResult = false; 402 403 try 404 { 405 bResult = NativeMethods.TransformKey(kvp.Key, kvp.Value, uRounds); 406 } 407 catch(Exception) { bResult = false; } 408 409 if(bResult) GetBuffers256(kvp, pBuf256, pKey256); 410 411 FreeArrays(kvp); 412 return bResult; 413 #endif 414 } 415 416 /// <summary> 417 /// Benchmark key transformation. 418 /// </summary> 419 /// <param name="uTimeMs">Number of milliseconds to perform the benchmark.</param> 420 /// <param name="puRounds">Number of transformations done.</param> 421 /// <returns>Returns <c>true</c>, if the benchmark was successful.</returns> TransformKeyBenchmark256(uint uTimeMs, out ulong puRounds)422 public static bool TransformKeyBenchmark256(uint uTimeMs, out ulong puRounds) 423 { 424 puRounds = 0; 425 426 #if KeePassUAP 427 return false; 428 #else 429 if(!m_bAllowNative) return false; 430 431 try { puRounds = NativeMethods.TransformKeyBenchmark(uTimeMs); } 432 catch(Exception) { return false; } 433 434 return true; 435 #endif 436 } 437 PrepareArrays256(byte[] pBuf256, byte[] pKey256)438 private static KeyValuePair<IntPtr, IntPtr> PrepareArrays256(byte[] pBuf256, 439 byte[] pKey256) 440 { 441 Debug.Assert((pBuf256 != null) && (pBuf256.Length == 32)); 442 if(pBuf256 == null) throw new ArgumentNullException("pBuf256"); 443 if(pBuf256.Length != 32) throw new ArgumentException(); 444 445 Debug.Assert((pKey256 != null) && (pKey256.Length == 32)); 446 if(pKey256 == null) throw new ArgumentNullException("pKey256"); 447 if(pKey256.Length != 32) throw new ArgumentException(); 448 449 IntPtr hBuf = Marshal.AllocHGlobal(pBuf256.Length); 450 Marshal.Copy(pBuf256, 0, hBuf, pBuf256.Length); 451 452 IntPtr hKey = Marshal.AllocHGlobal(pKey256.Length); 453 Marshal.Copy(pKey256, 0, hKey, pKey256.Length); 454 455 return new KeyValuePair<IntPtr, IntPtr>(hBuf, hKey); 456 } 457 GetBuffers256(KeyValuePair<IntPtr, IntPtr> kvpSource, byte[] pbDestBuf, byte[] pbDestKey)458 private static void GetBuffers256(KeyValuePair<IntPtr, IntPtr> kvpSource, 459 byte[] pbDestBuf, byte[] pbDestKey) 460 { 461 if(kvpSource.Key != IntPtr.Zero) 462 Marshal.Copy(kvpSource.Key, pbDestBuf, 0, pbDestBuf.Length); 463 464 if(kvpSource.Value != IntPtr.Zero) 465 Marshal.Copy(kvpSource.Value, pbDestKey, 0, pbDestKey.Length); 466 } 467 FreeArrays(KeyValuePair<IntPtr, IntPtr> kvpPointers)468 private static void FreeArrays(KeyValuePair<IntPtr, IntPtr> kvpPointers) 469 { 470 if(kvpPointers.Key != IntPtr.Zero) 471 Marshal.FreeHGlobal(kvpPointers.Key); 472 473 if(kvpPointers.Value != IntPtr.Zero) 474 Marshal.FreeHGlobal(kvpPointers.Value); 475 } 476 477 // internal static Type GetUwpType(string strType) 478 // { 479 // if(string.IsNullOrEmpty(strType)) { Debug.Assert(false); return null; } 480 // // https://referencesource.microsoft.com/#mscorlib/system/runtime/interopservices/windowsruntime/winrtclassactivator.cs 481 // return Type.GetType(strType + ", Windows, ContentType=WindowsRuntime", false); 482 // } 483 484 // Cf. DecodeArgsToData EncodeDataToArgs(string strData)485 internal static string EncodeDataToArgs(string strData) 486 { 487 if(strData == null) { Debug.Assert(false); return string.Empty; } 488 489 if(MonoWorkarounds.IsRequired(3471228285U) && IsUnix()) 490 { 491 string str = strData; 492 493 str = str.Replace("\\", "\\\\"); 494 str = str.Replace("\"", "\\\""); 495 496 // Whether '\'' needs to be encoded depends on the context 497 // (e.g. surrounding quotes); as we do not know what the 498 // caller does with the returned string, we assume that 499 // it will be used in a context where '\'' must not be 500 // encoded; this behavior is documented 501 // str = str.Replace("\'", "\\\'"); 502 503 return str; 504 } 505 506 // SHELLEXECUTEINFOW structure documentation: 507 // https://docs.microsoft.com/en-us/windows/desktop/api/shellapi/ns-shellapi-shellexecuteinfow 508 // return strData.Replace("\"", "\"\"\""); 509 510 // Microsoft C/C++ startup code: 511 // https://docs.microsoft.com/en-us/cpp/cpp/parsing-cpp-command-line-arguments 512 // CommandLineToArgvW function: 513 // https://docs.microsoft.com/en-us/windows/desktop/api/shellapi/nf-shellapi-commandlinetoargvw 514 515 StringBuilder sb = new StringBuilder(); 516 int i = 0; 517 while(i < strData.Length) 518 { 519 char ch = strData[i++]; 520 521 if(ch == '\\') 522 { 523 int cBackslashes = 1; 524 while((i < strData.Length) && (strData[i] == '\\')) 525 { 526 ++cBackslashes; 527 ++i; 528 } 529 530 if(i == strData.Length) 531 sb.Append('\\', cBackslashes); // Assume no quote follows 532 else if(strData[i] == '\"') 533 { 534 sb.Append('\\', (cBackslashes * 2) + 1); 535 sb.Append('\"'); 536 ++i; 537 } 538 else sb.Append('\\', cBackslashes); 539 } 540 else if(ch == '\"') sb.Append("\\\""); 541 else sb.Append(ch); 542 } 543 544 return sb.ToString(); 545 } 546 547 // Cf. EncodeDataToArgs DecodeArgsToData(string strArgs)548 internal static string DecodeArgsToData(string strArgs) 549 { 550 if(strArgs == null) { Debug.Assert(false); return string.Empty; } 551 552 Debug.Assert(StrUtil.Count(strArgs, "\"") == StrUtil.Count(strArgs, "\\\"")); 553 554 if(MonoWorkarounds.IsRequired(3471228285U) && IsUnix()) 555 { 556 string str = strArgs; 557 558 str = str.Replace("\\\"", "\""); 559 str = str.Replace("\\\\", "\\"); 560 561 return str; 562 } 563 564 StringBuilder sb = new StringBuilder(); 565 int i = 0; 566 while(i < strArgs.Length) 567 { 568 char ch = strArgs[i++]; 569 570 if(ch == '\\') 571 { 572 int cBackslashes = 1; 573 while((i < strArgs.Length) && (strArgs[i] == '\\')) 574 { 575 ++cBackslashes; 576 ++i; 577 } 578 579 if(i == strArgs.Length) 580 sb.Append('\\', cBackslashes); // Assume no quote follows 581 else if(strArgs[i] == '\"') 582 { 583 Debug.Assert((cBackslashes & 1) == 1); 584 sb.Append('\\', (cBackslashes - 1) / 2); 585 sb.Append('\"'); 586 ++i; 587 } 588 else sb.Append('\\', cBackslashes); 589 } 590 else sb.Append(ch); 591 } 592 593 return sb.ToString(); 594 } 595 StartProcess(string strFile)596 internal static void StartProcess(string strFile) 597 { 598 StartProcess(strFile, null); 599 } 600 StartProcess(string strFile, string strArgs)601 internal static void StartProcess(string strFile, string strArgs) 602 { 603 ProcessStartInfo psi = new ProcessStartInfo(); 604 if(!string.IsNullOrEmpty(strFile)) psi.FileName = strFile; 605 if(!string.IsNullOrEmpty(strArgs)) psi.Arguments = strArgs; 606 psi.UseShellExecute = true; 607 608 StartProcess(psi); 609 } 610 StartProcess(ProcessStartInfo psi)611 internal static void StartProcess(ProcessStartInfo psi) 612 { 613 Process p = StartProcessEx(psi); 614 615 try { if(p != null) p.Dispose(); } 616 catch(Exception) { Debug.Assert(false); } 617 } 618 StartProcessEx(ProcessStartInfo psi)619 internal static Process StartProcessEx(ProcessStartInfo psi) 620 { 621 if(psi == null) { Debug.Assert(false); return null; } 622 623 string strFileOrg = psi.FileName; 624 if(string.IsNullOrEmpty(strFileOrg)) { Debug.Assert(false); return null; } 625 string strArgsOrg = psi.Arguments; 626 627 Process p; 628 try 629 { 630 CustomizeProcessStartInfo(psi); 631 p = Process.Start(psi); 632 } 633 finally 634 { 635 psi.FileName = strFileOrg; // Restore 636 psi.Arguments = strArgsOrg; 637 } 638 639 return p; 640 } 641 CustomizeProcessStartInfo(ProcessStartInfo psi)642 private static void CustomizeProcessStartInfo(ProcessStartInfo psi) 643 { 644 string strFile = psi.FileName, strArgs = psi.Arguments; 645 646 string[] vUrlEncSchemes = new string[] { 647 "file:", "ftp:", "ftps:", "http:", "https:", 648 "mailto:", "scp:", "sftp:" 649 }; 650 foreach(string strPfx in vUrlEncSchemes) 651 { 652 if(strFile.StartsWith(strPfx, StrUtil.CaseIgnoreCmp)) 653 { 654 Debug.Assert(string.IsNullOrEmpty(strArgs)); 655 656 strFile = strFile.Replace("\"", "%22"); 657 strFile = strFile.Replace("\'", "%27"); 658 strFile = strFile.Replace("\\", "%5C"); 659 break; 660 } 661 } 662 663 if(IsUnix()) 664 { 665 if(MonoWorkarounds.IsRequired(19836) && string.IsNullOrEmpty(strArgs)) 666 { 667 if(Regex.IsMatch(strFile, "^[a-zA-Z][a-zA-Z0-9\\+\\-\\.]*:", 668 RegexOptions.Singleline) || 669 strFile.EndsWith(".html", StrUtil.CaseIgnoreCmp)) 670 { 671 bool bMacOSX = (GetPlatformID() == PlatformID.MacOSX); 672 673 strArgs = "\"" + EncodeDataToArgs(strFile) + "\""; 674 strFile = (bMacOSX ? "open" : "xdg-open"); 675 } 676 } 677 678 // Mono's Process.Start method replaces '\\' by '/', 679 // which may cause a different file to be executed; 680 // therefore, we refuse to start such files 681 if(strFile.Contains("\\") && MonoWorkarounds.IsRequired(190417)) 682 throw new ArgumentException(KLRes.PathBackslash); 683 684 strFile = strFile.Replace("\\", "\\\\"); // If WA not required 685 strFile = strFile.Replace("\"", "\\\""); 686 } 687 688 psi.FileName = strFile; 689 psi.Arguments = strArgs; 690 } 691 } 692 } 693