PageRenderTime 83ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 1ms

/Katmai_October2009_Refresh3/Administrator/ascmd/CS/ascmd/ascmd.cs

#
C# | 2686 lines | 1940 code | 227 blank | 519 comment | 392 complexity | cc822daa9a79f5e271700b0a15e29f2b MD5 | raw file
  1. //=====================================================================
  2. //
  3. // File: ascmd.cs
  4. // Summary: A command-line utility to execute Analysis Services XMLA scripts
  5. // or MDX queries
  6. // Authors: Dave Wickert (dwickert@microsoft.com)
  7. // Date: 03-Jan-2006
  8. //
  9. // Change history:
  10. // -Jul-2007: EricJ : Added input parsing from xml format.
  11. // Added -oResultStat for output result statistics.
  12. // Added RandomSeed, ThinkTimeMin, ThinkTimeMax, ConnectWaitMin, ConnectWaitMax.
  13. // 07-Apr-2008: dwickert : Added scripting variable support for RandomSeed, ThinkTimeMin,
  14. // ThinkTimeMax, ConnectWaitMin, ConnectWaitMax.
  15. // 10-Apr-2008: dwickert : Added -Xf (exit file) option with matching scripting variable support
  16. // 18-Aug-2008: dwickert : integrated and prep'ed for 2008 support
  17. // 20-Aug-2008: dwickert : Released SQL 2008 beta1
  18. // 27-Aug-2008: dwickert : Fixed bug in -U logon processing (was disposing of token handle early)
  19. // 28-Aug-2008: dwickert : Finalized support SQL 2008 (+ various naming issues that fxcop found)
  20. // 02-Sep-2008: dwickert : Fixed bug with input file name as query name when multiple input files used
  21. // @TODO: (1) add InputPatternBegin, InputPatternEnd
  22. // @TODO: (2) add support for "go;" with semicolon
  23. // @TODO: (3) Consider moving ResultStat to separate utility
  24. // @TODO: (4) Why does it not measure query duration?
  25. // @TODO: (5) Add tag to output to csv file verbatim info, to help create output for excel
  26. //
  27. //---------------------------------------------------------------------
  28. //
  29. // This file is part of the Microsoft SQL Server Code Samples.
  30. // Copyright (C) Microsoft Corporation. All rights reserved.
  31. //
  32. //This source code is intended only as a supplement to Microsoft
  33. //Development Tools and/or on-line documentation. See these other
  34. //materials for detailed information regarding Microsoft code samples.
  35. //
  36. //THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF
  37. //ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
  38. //THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
  39. //PARTICULAR PURPOSE.
  40. //
  41. //=====================================================================
  42. // Note: the code below contains additional lines to check for strong
  43. // naming convention of obects, e.g. server names, usernames, passwords, etc.
  44. // However, since we don't know what you guidelines will be we were not
  45. // able to assume your specific values. Free from to change ParseArgs and add
  46. // minimum, maximum and invalid object characters based on your
  47. // individual naming conventions.
  48. // Line length (limit to 79 characters for formatting)
  49. // 1 2 3 4 5 6 7
  50. //34567890123456789012345678901234567890123456789012345678901234567890123456789
  51. using System;
  52. using System.Web;
  53. using System.Collections.Generic;
  54. using System.Diagnostics;
  55. using System.IO;
  56. using System.Reflection;
  57. using System.Text;
  58. using System.Text.RegularExpressions;
  59. using System.Xml;
  60. using System.Globalization;
  61. using System.Threading;
  62. using Microsoft.Samples.SqlServer.Properties; // the locSting resources
  63. using Microsoft.AnalysisServices; //AMO
  64. using Microsoft.AnalysisServices.AdomdClient; //ADOMD.NET
  65. // needed for Logon Win32 API call and user impersonation //
  66. using System.Runtime.InteropServices;
  67. using System.Security.Principal;
  68. using System.Security.Permissions;
  69. // needed for random number generation
  70. using Microsoft.Samples.SqlServer.ASCmd.RandomHelper;
  71. [assembly: CLSCompliant(true)]
  72. namespace Microsoft.Samples.SqlServer.ASCmd
  73. {
  74. internal static class NativeMethods
  75. {
  76. [DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
  77. [return: MarshalAs(UnmanagedType.Bool)]
  78. public static extern bool LogonUser(String lpszUsername, String lpszDomain, String lpszPassw_outord,
  79. int dwLogonType, int dwLogonProvider, ref IntPtr phToken);
  80. [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
  81. [return: MarshalAs(UnmanagedType.Bool)]
  82. public static extern bool CloseHandle(IntPtr handle);
  83. }
  84. class CmdMain
  85. {
  86. #region Assembly Attribute Accessors
  87. //static string AssemblyTitle // not used yet
  88. //{
  89. // get
  90. // {
  91. // // Get all Title attributes on this assembly
  92. // object[] attributes = System.Reflection.Assembly.GetExecutingAssembly().GetCustomAttributes(typeof(AssemblyTitleAttribute), false);
  93. // // If there is at least one Title attribute
  94. // if (attributes.Length > 0)
  95. // {
  96. // // Select the first one
  97. // AssemblyTitleAttribute titleAttribute = (AssemblyTitleAttribute)attributes[0];
  98. // // If it is not an empty string, return it
  99. // if (titleAttribute.Title != "")
  100. // return titleAttribute.Title;
  101. // }
  102. // // If there was no Title attribute, or if the Title attribute was the empty string, return the .exe name
  103. // return System.IO.Path.GetFileNameWithoutExtension(System.Reflection.Assembly.GetExecutingAssembly().CodeBase);
  104. // }
  105. //}
  106. static string AssemblyVersion
  107. {
  108. get
  109. {
  110. return System.Reflection.Assembly.GetExecutingAssembly().GetName().Version.ToString();
  111. }
  112. }
  113. static string AssemblyDescription
  114. {
  115. get
  116. {
  117. // Get all Description attributes on this assembly
  118. object[] attributes = System.Reflection.Assembly.GetExecutingAssembly().GetCustomAttributes(typeof(AssemblyDescriptionAttribute), false);
  119. // If there aren't any Description attributes, return an empty string
  120. if (attributes.Length == 0)
  121. return "";
  122. // If there is a Description attribute, return its value
  123. return ((AssemblyDescriptionAttribute)attributes[0]).Description;
  124. }
  125. }
  126. static string AssemblyProduct // not used yet
  127. {
  128. get
  129. {
  130. // Get all Product attributes on this assembly
  131. object[] attributes = System.Reflection.Assembly.GetExecutingAssembly().GetCustomAttributes(typeof(AssemblyProductAttribute), false);
  132. // If there aren't any Product attributes, return an empty string
  133. if (attributes.Length == 0)
  134. return "";
  135. // If there is a Product attribute, return its value
  136. return ((AssemblyProductAttribute)attributes[0]).Product;
  137. }
  138. }
  139. static string AssemblyCopyright
  140. {
  141. get
  142. {
  143. // Get all Copyright attributes on this assembly
  144. object[] attributes = System.Reflection.Assembly.GetExecutingAssembly().GetCustomAttributes(typeof(AssemblyCopyrightAttribute), false);
  145. // If there aren't any Copyright attributes, return an empty string
  146. if (attributes.Length == 0)
  147. return "";
  148. // If there is a Copyright attribute, return its value
  149. return ((AssemblyCopyrightAttribute)attributes[0]).Copyright;
  150. }
  151. }
  152. //static string AssemblyCompany // not used yet
  153. //{
  154. // get
  155. // {
  156. // // Get all Company attributes on this assembly
  157. // object[] attributes = System.Reflection.Assembly.GetExecutingAssembly().GetCustomAttributes(typeof(AssemblyCompanyAttribute), false);
  158. // // If there aren't any Company attributes, return an empty string
  159. // if (attributes.Length == 0)
  160. // return "";
  161. // // If there is a Company attribute, return its value
  162. // return ((AssemblyCompanyAttribute)attributes[0]).Company;
  163. // }
  164. //}
  165. static string AssemblyProcessorArchitecture
  166. {
  167. get
  168. {
  169. return System.Reflection.Assembly.GetExecutingAssembly().GetName().ProcessorArchitecture.ToString();
  170. }
  171. }
  172. #endregion
  173. #region Class Variables
  174. // message handling for loc strings
  175. static string Msg(string locString) { return (AssemblyProduct + ": " + locString); }
  176. // What options have been seen on the command line
  177. static bool Option_U_specified;
  178. static bool Option_P_specified;
  179. static bool Option_S_specified;
  180. static bool Option_d_specified;
  181. static bool Option_t_specified;
  182. static bool Option_tc_specified;
  183. static bool Option_i_specified;
  184. static bool Option_o_specified;
  185. static bool Option_oResultStat_specified;
  186. static bool Option_NoResultStatHeader_specified;
  187. static bool Option_RunInfo_specified;
  188. static bool Option_RandomSeed_specified;
  189. static bool Option_ThinkTimeMin_specified;
  190. static bool Option_ThinkTimeMax_specified;
  191. static bool Option_ConnectWaitMin_specified;
  192. static bool Option_ConnectWaitMax_specified;
  193. static bool Option_T_specified;
  194. static bool Option_Tt_specifed;
  195. static bool Option_Tf_specified;
  196. static bool Option_Tl_specified;
  197. static bool Option_Td_specified;
  198. static bool Option_Q_specified;
  199. static bool Option_xc_specified;
  200. static bool Option_v_specified;
  201. static bool Option_Xf_specified;
  202. // Variables for options
  203. static string UserName = "";
  204. static string Domain = "";
  205. static string Password = "";
  206. static string Server = "localhost";
  207. static string Instance = "";
  208. static string Database = "";
  209. static string InputFile = "";
  210. static string OutputFile = "";
  211. static string OutputResultStatFile = "";
  212. static string RunInfo = "";
  213. static string TraceFile = "";
  214. static string Query = "";
  215. static string ExtendedConnectstring = "";
  216. static string TraceFormat = "csv"; // use the -f option
  217. static string Timeout = "";
  218. static string ConnectTimeout = "";
  219. static string TraceTimeout = "5"; // trace is finished if no activity for 5 seconds
  220. static bool httpConnection;
  221. static int RandomSeed;
  222. static int ThinkTimeMin;
  223. static int ThinkTimeMax;
  224. static int ConnectWaitMin;
  225. static int ConnectWaitMax;
  226. static string ExitFile = ""; // exit file (if specified)
  227. // provider to use -- should we have this an an option (i.e. -p) ??
  228. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
  229. const string Provider = "MSOLAP.3"; // force AS2K5 provider
  230. // Variables for read/writing
  231. static StringBuilder sb = new StringBuilder(); // input stream
  232. static StreamWriter sw_out; //Output file
  233. static StreamWriter sw_outResultStat; //Output file
  234. static bool IsOutputFileOpen;
  235. static bool IsOutputResultStatFileOpen;
  236. static StreamWriter sw_trace; //Trace file
  237. static bool IsTraceFileOpen;
  238. static string TraceDelim = "|";
  239. static string TraceLevel = "high"; // default is to trace at the high level
  240. static long BeginEndBlockCount; // if 0 then OK to immediately finish trace
  241. static int TraceTimeoutCount; // if goes to 0, then finish trace regardless of BeginEndBlockCount
  242. static int TraceTimeoutCountReset; // the amount of time to wait once trace events stop
  243. static bool TraceStarted; // have the trace events started to flow?
  244. const int PollingInterval = 20; // Every 1/20th of a second (50ms) see if trace is done (BeginEndBlockCount drives to 0)
  245. static bool ExceptionSeen; // have we seen an exception in the xmla return stream
  246. static bool _DEBUGMODE; // Set to indicate that we need debug messages outputted
  247. static HighPerformanceRandom perfRandom;
  248. // substitution table -- includes -v entries plus command-line arguments
  249. static Dictionary<string, string> substituteWith =
  250. new Dictionary<string, string>(StringComparer.CurrentCultureIgnoreCase);
  251. // maximum timeout values (if used)
  252. const int maxTimeout = -1; // -1 means don't check
  253. // regular expressions used
  254. const string ScriptingVarRefRegex = @"\$\([a-zA-Z0-9_-]+\)";
  255. const string ScriptingVarNameRegex = @"^[a-zA-Z0-9_-]+=.*";
  256. const string XMLACommandRegex =
  257. @"(?sx)^(<Alter.*|
  258. <Attach.*|
  259. <Backup.*|
  260. <Batch.*|
  261. <BeginTransaction.*|
  262. <Cancel.*|
  263. <ClearCache.*|
  264. <CommitTransaction.*|
  265. <Create.*|
  266. <Delete.*|
  267. <DesignAggregations.*|
  268. <Detach.*|
  269. <Drop.*|
  270. <Insert.*|
  271. <Lock.*|
  272. <MergePartitions.*|
  273. <NotifyTableChange.*|
  274. <Process.*|
  275. <Restore.*|
  276. <RollbackTransaction.*|
  277. <SetPasswordEncryptionKey.*|
  278. <Statement.*|
  279. <Subscribe.*|
  280. <Synchronize.*|
  281. <Unlock.*|
  282. <Update.*|
  283. <UpdateCells.*)$";
  284. const string DiscoverRegex = @"(?sx)^<Discover.*$";
  285. const string ExecuteRegex = @"(?sx)^<Execute.*$";
  286. const string BatchRegex = @"^[\w\W]*?[\r\n]*go\s";
  287. const string InputFileFormat = "**InputFile: {0}\n";
  288. const string InputFileRegex = @"(?sx)^\*\*InputFile: (?<inputFile>[\w\W]+?)\n(?<batchText>.*)$";
  289. // formats
  290. const string SoapEnvFormat =
  291. @"<Envelope xmlns='http://schemas.xmlsoap.org/soap/envelope/'>
  292. <Body>
  293. {0}
  294. </Body>
  295. </Envelope>";
  296. #endregion
  297. // --------------------------------------------------------------------
  298. // Main routine -- called with the command line arguments
  299. [SecurityPermission(SecurityAction.Assert, Flags = SecurityPermissionFlag.UnmanagedCode)]
  300. static int Main(string[] args)
  301. {
  302. //HighPerformanceRandom.TestShowWithSeeds();
  303. //HighPerformanceRandom.Test2();
  304. try
  305. {
  306. Console.WriteLine(AssemblyDescription);
  307. Console.WriteLine(Properties.Resources.locVersion,
  308. AssemblyVersion, AssemblyProcessorArchitecture);
  309. Console.WriteLine(AssemblyCopyright);
  310. // Parse the command line argument list, verify the options and
  311. // open any requested files
  312. try
  313. {
  314. if (!ParseArgs(args))
  315. {
  316. return 1; // if it returns an error, just exit
  317. }
  318. }
  319. catch (Exception ex)
  320. {
  321. Console.WriteLine(Msg(Properties.Resources.locParseArgsErr),
  322. ex.Message);
  323. return 1; // error raised
  324. }
  325. // Take the input stream and substitute any scripting variables within it
  326. SubstituteScriptingVariables(sb);
  327. // Impersonate the -U username (if needed) and then **execute** the
  328. // query or script. Note: http connections set the UID and PWD in
  329. // the connect string
  330. if ((UserName.Length > 0) && !httpConnection)
  331. {
  332. try
  333. {
  334. // Need to impersonate the user first -- then ex
  335. const int LOGON32_PROVIDER_DEFAULT = 0;
  336. //This parameter causes LogonUser to create a primary token.
  337. const int LOGON32_LOGON_INTERACTIVE = 2;
  338. IntPtr tokenHandle = new IntPtr(0);
  339. tokenHandle = IntPtr.Zero;
  340. // Call LogonUser to obtain a handle to an access token.
  341. if (_DEBUGMODE) Console.WriteLine("Calling LogonUser");
  342. bool returnValue = NativeMethods.LogonUser(UserName, Domain, Password,
  343. LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT,
  344. ref tokenHandle);
  345. if (false == returnValue)
  346. {
  347. int ret = Marshal.GetLastWin32Error();
  348. Console.WriteLine(Msg(Properties.Resources.locLogonFailedErr), ret);
  349. throw new System.ComponentModel.Win32Exception(ret);
  350. }
  351. if (_DEBUGMODE) Console.WriteLine("Did LogonUser Succeed? " + (returnValue ? "Yes" : "No"));
  352. if (_DEBUGMODE) Console.WriteLine("Value of Windows NT token: " + tokenHandle);
  353. // Check the identity.
  354. if (_DEBUGMODE) Console.WriteLine("Before impersonation: " + WindowsIdentity.GetCurrent().Name);
  355. // Impersonate using the token handle returned by LogonUser.
  356. using (WindowsIdentity newId = new WindowsIdentity(tokenHandle))
  357. {
  358. WindowsImpersonationContext impersonatedUser = newId.Impersonate();
  359. ExecuteInput(ConnectString); // execute it
  360. impersonatedUser.Undo(); // which reverts to self
  361. }
  362. if (tokenHandle != IntPtr.Zero) NativeMethods.CloseHandle(tokenHandle);
  363. // Check the identity.
  364. if (_DEBUGMODE) Console.WriteLine("After impersonation: " + WindowsIdentity.GetCurrent().Name);
  365. }
  366. catch (Exception ex)
  367. {
  368. Console.WriteLine(Msg(Properties.Resources.locImpersonateErr), ex.Message);
  369. throw;
  370. }
  371. }
  372. else // no impersonation needed
  373. {
  374. try
  375. {
  376. ExecuteInput(ConnectString); // execute it
  377. }
  378. catch (Exception ex)
  379. {
  380. Console.WriteLine(Msg(Properties.Resources.locImpersonateErr), ex.Message);
  381. throw;
  382. }
  383. }
  384. CloseFiles();
  385. }
  386. catch (Exception ex)
  387. {
  388. Console.WriteLine(Msg(Properties.Resources.locFailedErr), ex.Message);
  389. return 1; // error raised
  390. }
  391. if (Option_o_specified && ExceptionSeen)
  392. Console.WriteLine(Msg(Properties.Resources.locCheckOutputForErr));
  393. return (ExceptionSeen) ? 1 : 0;
  394. }
  395. // Regex match against input string??
  396. private static bool InputMatch(string regexString, StringBuilder sb)
  397. {
  398. string s = sb.ToString();
  399. bool b = Regex.IsMatch(s, regexString);
  400. return b;
  401. }
  402. // Rest of the functions below are supporting routines.
  403. // --------------------------------------------------------------------
  404. // Supporting routine -- perform scripting variable substitution
  405. private static void SubstituteScriptingVariables(StringBuilder sb)
  406. {
  407. Regex expression = new Regex(ScriptingVarRefRegex);
  408. foreach (Match m in expression.Matches(sb.ToString()))
  409. {
  410. // Pick out just the scripting variable name by remove leading and
  411. // trailing characters of the scripting variable
  412. string name = m.ToString().Replace("$(", "").Replace(")", "");
  413. // Perform the substitution of scripting variable to its value
  414. if (substituteWith.ContainsKey(name))
  415. {
  416. // -v or system-reserved replacement
  417. sb.Replace(m.ToString(), substituteWith[name]);
  418. }
  419. else if ((Environment.GetEnvironmentVariable(name) != null) &&
  420. (!name.StartsWith("ASCMD", StringComparison.CurrentCultureIgnoreCase)))
  421. {
  422. // environment variable replacement (cannot be used for system-reserved)
  423. sb.Replace(m.ToString(), Environment.GetEnvironmentVariable(name));
  424. }
  425. else
  426. {
  427. // no match found, so eliminate the scripting variable by
  428. // replacing it with a blank string
  429. sb.Replace(m.ToString(), "");
  430. }
  431. }
  432. if (_DEBUGMODE) Console.WriteLine("After substitution, final input is: " + sb.ToString());
  433. }
  434. // --------------------------------------------------------------------
  435. // Supporting routine -- determine what the connect string should be
  436. private static string ConnectString
  437. {
  438. get
  439. {
  440. string cs = "";
  441. // Build the connect string as required
  442. // Note: some parameters might have legimite embedded doublequotes "
  443. // if so, we marked them with // ** tag
  444. cs += "Provider=" + Provider;
  445. if (httpConnection)
  446. {
  447. cs += ";Data Source=\"" + Server + "\"";
  448. }
  449. else
  450. {
  451. cs += ";Data Source=\"" + Server;
  452. if (Instance.Length > 0) cs += "\\" + Instance;
  453. cs += "\"";
  454. }
  455. if (Database.Length > 0) cs += ";Database=\"" + Database.Replace("\"", "\"\"") + "\""; //**
  456. // Add http information, if requested
  457. if (httpConnection && (UserName.Length > 0))
  458. {
  459. cs += ";UID=\"" + ((Domain.Length > 0) ? Domain + "\\" + UserName : UserName) + "\"";
  460. cs += ";PWD=\"" + Password.Replace("\"", "\"\"") + "\""; // **
  461. }
  462. // Add timeout information, if requested
  463. if (Timeout.Length > 0)
  464. cs += ";Timeout=" + Timeout.ToString(CultureInfo.CurrentCulture);
  465. if (ConnectTimeout.Length > 0)
  466. cs += ";Connect Timeout=" + ConnectTimeout.ToString(CultureInfo.CurrentCulture);
  467. // Add extended connectstring option
  468. if (ExtendedConnectstring.Length > 0)
  469. cs += ";" + ExtendedConnectstring.Replace("\"", "\"\""); // **
  470. // Finally, add our application name :-)
  471. cs += ";SspropInitAppName=" + AssemblyProduct;
  472. if (_DEBUGMODE) Console.WriteLine("Connect string: {0}", cs);
  473. return cs;
  474. }
  475. }
  476. // --------------------------------------------------------------------
  477. // Execute an input statement (MDX query or XMLA script)
  478. // *** This is the real work of the program -- the rest is supporting functions ***
  479. private static void ExecuteInput(string ConnectionString)
  480. {
  481. // Wait a random time before connecting.
  482. // The purpose is during a multi-user load test,
  483. // to gradually ramp up and overcome potential problems of not being able
  484. // to connect all clients.
  485. // fix up limits of connect time
  486. if (ConnectWaitMin < 0) ConnectWaitMin = 0;
  487. if (ConnectWaitMax < ConnectWaitMin) ConnectWaitMax = ConnectWaitMin;
  488. if (perfRandom == null) perfRandom = new HighPerformanceRandom(RandomSeed);
  489. // if we have connect time, then wait it
  490. if (ConnectWaitMax > 0)
  491. {
  492. int iSleepTime = perfRandom.Next(ConnectWaitMin * 1000, ConnectWaitMax * 1000);
  493. if (_DEBUGMODE) Console.WriteLine("ConnectWait, begin sleeping {0} millisec.", iSleepTime);
  494. System.Threading.Thread.Sleep(iSleepTime);
  495. if (_DEBUGMODE) Console.WriteLine("ConnectWait, end sleeping.");
  496. }
  497. Server oServer = new Server();
  498. //Open up a connection
  499. try
  500. {
  501. oServer.Connect(ConnectionString);
  502. }
  503. catch (Exception ex)
  504. {
  505. Console.WriteLine(Msg(Properties.Resources.locAmoConnErr), ConnectionString, ex.Message);
  506. throw;
  507. }
  508. // Try to execute the query/script
  509. try
  510. {
  511. // Setup to trace events during execution (only if level is not "duration")
  512. if (Option_T_specified && !(TraceLevel == "duration") && !(TraceLevel == "duration-result"))
  513. {
  514. // Initialize trace handler
  515. oServer.SessionTrace.OnEvent += new TraceEventHandler(SessionTrace_OnEvent);
  516. //Write some status stuff
  517. if (_DEBUGMODE) Console.WriteLine("Trace connection status to " + Server + " := " + oServer.Connected.ToString(CultureInfo.CurrentCulture));
  518. if (_DEBUGMODE) Console.WriteLine("Server := " + oServer.Name + " (Session:" + oServer.SessionID.ToString(CultureInfo.CurrentCulture) + ")");
  519. // Start the trace
  520. oServer.SessionTrace.Start();
  521. }
  522. // Execute the input batches
  523. ExecuteBatches(oServer);
  524. // Stop the trace (if running)
  525. if (!oServer.SessionTrace.IsStarted) oServer.SessionTrace.Stop();
  526. }
  527. catch (Exception ex)
  528. {
  529. Console.WriteLine(Msg(Properties.Resources.locAdomdExecutionErr), ex.Message);
  530. oServer.Dispose();
  531. throw;
  532. }
  533. //Disconnect and end the session on the server
  534. try
  535. {
  536. oServer.Disconnect(true);
  537. }
  538. catch (Exception ex)
  539. {
  540. Console.WriteLine(Msg(Properties.Resources.locAmoDisconnErr), ex.Message);
  541. throw;
  542. }
  543. oServer.Dispose();
  544. }
  545. // --------------------------------------------------------------------
  546. // Execute the batches in the input stream (sb)
  547. // A batch is a portion of the input stream separated by "GO" commands, i.e.
  548. // <batch>
  549. // GO
  550. // <batch>
  551. // GO
  552. //
  553. private static void ExecuteBatches(Server oServer)
  554. {
  555. Regex expression = new Regex(BatchRegex,
  556. RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.Multiline);
  557. // pickup the input stream, append a GO command and parse it
  558. MatchCollection matches = expression.Matches(sb.ToString() + "\ngo\n");
  559. if (matches.Count > 1) Output(null, DateTime.Now, DateTime.Now, "<multiple-batches>");
  560. bool DidFirstQuery = false;
  561. foreach (Match m in matches)
  562. {
  563. // Wait a random think time after each query.
  564. // The purpose is during a multi-user load test.
  565. if (DidFirstQuery)
  566. {
  567. // Does the exit file exist? If so, break out of the foreach Match loop early
  568. if (File.Exists(ExitFile)) break;
  569. // Fix up limits of think time
  570. if (ThinkTimeMin < 0) ThinkTimeMin = 0;
  571. if (ThinkTimeMax < ThinkTimeMin) ThinkTimeMax = ThinkTimeMin;
  572. if (perfRandom == null) perfRandom = new HighPerformanceRandom(RandomSeed);
  573. // If we have a think time, then wait it
  574. if (ThinkTimeMax > 0)
  575. {
  576. int iSleepTime = perfRandom.Next(ThinkTimeMin * 1000, ThinkTimeMax * 1000);
  577. if (_DEBUGMODE) Console.WriteLine("ThinkTime, begin sleeping {0} millisec.", iSleepTime);
  578. System.Threading.Thread.Sleep(iSleepTime);
  579. if (_DEBUGMODE) Console.WriteLine("ThinkTime, end sleeping.");
  580. }
  581. }
  582. // Pick out just the scripting variable name by remove leading and
  583. // trailing characters of the scripting variable
  584. string fullText = m.Value;
  585. StringBuilder batch = new StringBuilder(fullText.Substring(0, fullText.Length - 3).Trim());
  586. if (_DEBUGMODE) Console.WriteLine("Batch input is: " + batch.ToString());
  587. if (batch.Length > 0) ExecuteBatch(oServer, InputFileMarker(batch)); // fire the batch
  588. DidFirstQuery = true;
  589. } // loop for next batch
  590. if (matches.Count > 1) Output(null, DateTime.Now, DateTime.Now, "</multiple-batches>");
  591. }
  592. private static StringBuilder InputFileMarker(StringBuilder inp)
  593. {
  594. try
  595. {
  596. Regex expression = new Regex(InputFileRegex,
  597. RegexOptions.Compiled | RegexOptions.IgnoreCase); // | RegexOptions.Multiline);
  598. // pickup the input stream, append a GO command and parse it
  599. MatchCollection matches = expression.Matches(inp.ToString());
  600. if (matches.Count != 1) return inp;
  601. InputFile = matches[0].Groups["inputFile"].ToString();
  602. StringBuilder sb = new StringBuilder(matches[0].Groups["batchText"].ToString());
  603. if (_DEBUGMODE) Console.WriteLine("Setting Input File to: {0}\nSetting batch to: {1}", InputFile, sb.ToString());
  604. return sb;
  605. }
  606. catch (Exception ex)
  607. {
  608. Console.WriteLine("Parse error: {0}", ex.Message);
  609. throw;
  610. }
  611. }
  612. // Now execute the batch itself (which is the lowest level of command to ascmd)
  613. private static void ExecuteBatch(Server oServer, StringBuilder inp)
  614. {
  615. // The result string for the execution
  616. string Result;
  617. // Wrap a stopwatch around execution
  618. DateTime StartTime = DateTime.Now;
  619. DateTime EndTime = DateTime.Now;
  620. Stopwatch sw = new Stopwatch();
  621. // If this is not an XMLA command (or Discover/Execute raw XMLA request) then
  622. // we assume that it is an un-encoded Statement without the XML tag.
  623. // This allows the end-user the convience of just entering a MDX or DMX statement
  624. // and we will wrap it for them.
  625. if (!InputMatch(XMLACommandRegex, inp) && !InputMatch(DiscoverRegex, inp) && !InputMatch(ExecuteRegex, inp))
  626. {
  627. string stmt = HttpUtility.HtmlEncode(inp.ToString());
  628. // Recreate in Input string -- wrapping with <Statement> tags
  629. inp = new StringBuilder(stmt.Length); // allocates space for new batch -- nothing in it
  630. inp.AppendFormat("<Statement>{0}</Statement>", stmt); // adds the batch input itself
  631. }
  632. // There are two different ways to execute the input: a raw XMLA request or an XMLA command.
  633. // If this is a raw request, then the batch starts with "Discover" or "Execute", else it is an XMLA command
  634. if (InputMatch(DiscoverRegex, inp) || InputMatch(ExecuteRegex, inp))
  635. {
  636. //--------------------------------------------------------------------------------
  637. // A raw XMLA request:
  638. // To run a custom full SOAP Envelope request on Analysis Services server, we
  639. // need to follow 5 steps:
  640. //
  641. // 1. Start the XML/A request and specify its type (Discover or Execute).
  642. // For native connections (direct over TCP/IP with DIME protocol), local
  643. // cube connections and stored procedures connections we don't need to
  644. // specify the XML/A request type in advance (an Undefined value is
  645. // available). But for HTTP and HTTPS connections we need to.
  646. //
  647. // 2. Write the xml request (as an xml soap envelope containing the command
  648. // for Analysis Services).
  649. //
  650. // 3. End the xml request; this will send the previously written request to the
  651. // server for execution.
  652. //
  653. // 4. Read/Parse the xml response from the server (with System.Xml.XmlReader).
  654. //
  655. // 5. Close the System.Xml.XmlReader from the previous step, to release the
  656. // connection to be used after.
  657. //--------------------------------------------------------------------------------
  658. XmlaRequestType r = XmlaRequestType.Undefined;
  659. if (InputMatch(DiscoverRegex, inp)) r = XmlaRequestType.Discover;
  660. if (InputMatch(ExecuteRegex, inp)) r = XmlaRequestType.Execute;
  661. // Wrap the request with a soap envelope and body
  662. string s = inp.ToString();
  663. s = String.Format(CultureInfo.CurrentCulture, SoapEnvFormat, s);
  664. // Start the stopwatch
  665. StartTime = DateTime.Now;
  666. sw.Start();
  667. // Execute the query/script and gather the results
  668. XmlWriter _xw = oServer.StartXmlaRequest(r);
  669. _xw.WriteRaw(s);
  670. using (XmlReader _xr = oServer.EndXmlaRequest())
  671. {
  672. // Stop the stopwatch
  673. sw.Stop();
  674. EndTime = DateTime.Now;
  675. // Move in the reader to where the real content begins
  676. _xr.MoveToContent();
  677. // Skip past the soap envelope and body (if they exist)
  678. if (!_xr.EOF && (_xr.Name == "soap:Envelope")) _xr.Read();
  679. if (!_xr.EOF && (_xr.Name == "soap:Body")) _xr.Read();
  680. // Gather the results and output them
  681. Result = GatherResults(_xr);
  682. // Close the System.Xml.XmlReader, to release the connection for
  683. // future use.
  684. _xr.Close();
  685. }
  686. }
  687. else
  688. {
  689. //--------------------------------------------------------------------------------
  690. // An XMLA Command request:
  691. StartTime = DateTime.Now;
  692. sw.Start(); // start the stopwatch
  693. XmlaResultCollection _xrc = oServer.Execute(inp.ToString());
  694. sw.Stop();
  695. EndTime = DateTime.Now;
  696. // Gather the results and output them
  697. Result = GatherResults(_xrc);
  698. }
  699. // Output the result
  700. Output(sw, StartTime, EndTime, Result);
  701. // If requested, wait for tracing to finish
  702. if (Option_T_specified)
  703. {
  704. if ((TraceLevel == "duration") || (TraceLevel == "duration-result"))
  705. {
  706. DurationTrace(sw, Result);
  707. }
  708. else
  709. {
  710. // is high, medium, low
  711. WaitForTraceToFinish();
  712. }
  713. }
  714. }
  715. // --------------------------------------------------------------------
  716. // Supporting routines -- gather the results
  717. private static string GatherResults(XmlReader Results)
  718. {
  719. string s = Results.ReadOuterXml();
  720. // -----------------------------------
  721. // Look for errors.
  722. // -----------------------------------
  723. // As an initial pass, we'll just look for some special tags in the
  724. // stream. This works because the underlying data in the xml stream
  725. // is html encoded -- thus the only tags that should exist is if there
  726. // are errors
  727. if (s.Contains("</soap:Fault>") || // xsd-detected errors
  728. s.Contains("</Exception>") // server-detected errors
  729. )
  730. {
  731. ExceptionSeen = true; // tell the main program that we saw errors
  732. }
  733. return s;
  734. }
  735. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1800:DoNotCastUnnecessarily")]
  736. private static string GatherResults(XmlaResultCollection Results)
  737. {
  738. // XML namespace constants used
  739. const string xmlns_xmla = "xmlns=\"urn:schemas-microsoft-com:xml-analysis\"";
  740. const string xmlns_multipleresults = "xmlns=\"http://schemas.microsoft.com/analysisservices/2003/xmla-multipleresults\"";
  741. const string xmlns_mddataset = "xmlns=\"urn:schemas-microsoft-com:xml-analysis:mddataset\"";
  742. const string xmlns_xsi = "xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"";
  743. const string xmlns_xsd = "xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\"";
  744. const string xmlns_empty = "xmlns=\"urn:schemas-microsoft-com:xml-analysis:empty\"";
  745. const string xmlns_exception = "xmlns=\"urn:schemas-microsoft-com:xml-analysis:exception\"";
  746. // Format strings constants used
  747. const string fs_Error = "<Error ErrorCode=\"{0}\" Description=\"{1}\" Source=\"{2}\" HelpFile=\"{3}\" />";
  748. const string fs_Warning = "<Warning WarningCode=\"{0}\" Description=\"{1}\" Source=\"{2}\" HelpFile=\"{3}\" />";
  749. // Start to build the output
  750. StringBuilder _output = new StringBuilder(
  751. String.Format(CultureInfo.CurrentCulture, "<return {0}>", xmlns_xmla));
  752. // If there are multiple resultsets, then add the grouping element
  753. if (Results.Count > 1)
  754. _output.Append(String.Format(CultureInfo.CurrentCulture,
  755. "<results {0}>", xmlns_multipleresults));
  756. // loop through each result in the result set
  757. foreach (XmlaResult _xr in Results)
  758. {
  759. // Is there a value for this result?
  760. if (_xr.Value.Length > 0)
  761. { // Yes, indicate its type
  762. _output.Append(String.Format(CultureInfo.CurrentCulture,
  763. "<root {0} {1} {2}>", xmlns_mddataset, xmlns_xsi, xmlns_xsd));
  764. _output.Append(_xr.Value); // include the value in the stream
  765. }
  766. else
  767. { // Nope, output the empty set for the root
  768. _output.Append(String.Format(CultureInfo.CurrentCulture,
  769. "<root {0}>", xmlns_empty));
  770. }
  771. // Do we have some messages associated with the result? If so, output them
  772. if (_xr.Messages.Count > 0)
  773. {
  774. if (ErrorsExist(_xr))
  775. _output.Append(String.Format(CultureInfo.CurrentCulture,
  776. "<Exception {0} />", xmlns_exception));
  777. // Output the messages
  778. _output.Append(String.Format(CultureInfo.CurrentCulture,
  779. "<Messages {0}>", xmlns_exception));
  780. foreach (XmlaMessage _xm in _xr.Messages)
  781. {
  782. if (_xm is XmlaError) // determine type type
  783. {
  784. ExceptionSeen = true; // ERROR bubbles up for the "main" return value
  785. int ErrorCode = ((XmlaError)_xm).ErrorCode;
  786. _output.Append(String.Format(CultureInfo.CurrentCulture, fs_Error,
  787. ((uint)ErrorCode).ToString(CultureInfo.CurrentCulture),
  788. HttpUtility.HtmlEncode(_xm.Description), _xm.Source, _xm.HelpFile));
  789. }
  790. else
  791. {
  792. int WarningCode = ((XmlaWarning)_xm).WarningCode;
  793. _output.Append(String.Format(CultureInfo.CurrentCulture, fs_Warning,
  794. ((uint)WarningCode).ToString(CultureInfo.CurrentCulture),
  795. HttpUtility.HtmlEncode(_xm.Description), _xm.Source, _xm.HelpFile));
  796. }
  797. }
  798. _output.Append("</Messages>");
  799. }
  800. _output.Append("</root>");
  801. }
  802. if (Results.Count > 1) _output.Append("</results>");
  803. _output.Append("</return>");
  804. // Return the string we've constructed
  805. return _output.ToString();
  806. }
  807. private static bool ErrorsExist(XmlaResult _xr)
  808. {
  809. bool ret = false;
  810. foreach (XmlaMessage _xm in _xr.Messages)
  811. {
  812. if (_xm is XmlaError) ret = true;
  813. }
  814. return ret;
  815. }
  816. private static void WaitForTraceToFinish()
  817. {
  818. int delay = Int32.Parse(TraceTimeout, CultureInfo.CurrentCulture);
  819. TraceTimeoutCount = TraceTimeoutCountReset = delay * PollingInterval; // TraceTimeoutCount is in 1/4 seconds
  820. // Wait for trace to start to flow
  821. while (!TraceStarted) { Thread.Sleep(1); } // loop every 20ms waiting for trace to start
  822. // Wait BeginEndBlockCount becomes 0 or TraceTimeoutCount expires
  823. while (TraceTimeoutCount > 0)
  824. { // loop forever until TraceFinished
  825. if (BeginEndBlockCount == 0) return;
  826. Thread.Sleep(1000 / PollingInterval); // wait a polling interval
  827. TraceTimeoutCount--;
  828. }
  829. // TraceTimeoutCount expired -- just exit
  830. }
  831. // -------------------------------------------------------------------
  832. // Supporting routines which handle the trace events
  833. // Remove control characters, i.e. LF, CR, TAB, etc. and replace them with spaces
  834. private static string RemoveControlChars(string s)
  835. {
  836. StringBuilder sb = new StringBuilder(s);
  837. for (int i = 0; i < s.Length; i++)
  838. {
  839. if (Char.IsControl(sb[i]))
  840. sb[i] = ' '; // replace all control characters with a space
  841. }
  842. return sb.ToString();
  843. }
  844. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity")]
  845. static void SessionTrace_OnEvent(object sender, TraceEventArgs e)
  846. {
  847. string EventClass = e.EventClass.ToString();
  848. //if (_DEBUGMODE) Console.WriteLine("SessionTrace_OnEvent fired");
  849. TraceStarted = true; // indicated that we have started to see trace events
  850. // keep the begin-end block count
  851. if (EventClass.Contains("Begin")) BeginEndBlockCount++;
  852. if (EventClass.Contains("End") && (BeginEndBlockCount > 0)) BeginEndBlockCount--;
  853. // based on the trace level, decide if we should record the event
  854. // high just falls through -- everything is recorded
  855. if (TraceLevel == "medium" || TraceLevel == "low")
  856. {
  857. if ((EventClass == "ProgressReportCurrent") ||
  858. //(EventClass == "ProgressReportBegin") ||
  859. //(EventClass == "ProgressReportEnd") ||
  860. (EventClass == "Notification")) return; // ignore this event
  861. }
  862. if (TraceLevel == "low")
  863. {
  864. if (!(EventClass.Contains("End") ||
  865. EventClass.Contains("Error"))) return; // if EventClass doesn't contain 'End' or 'Error', ignore this event
  866. }
  867. switch (TraceFormat)
  868. {
  869. case "text": // a text interpretation of the event
  870. {
  871. StringBuilder tm = new StringBuilder(); // trace message
  872. tm.Append(e.CurrentTime.ToString(CultureInfo.CurrentCulture));
  873. tm.Append(", " + e.EventClass.ToString());
  874. tm.Append("." + e.EventSubclass.ToString());
  875. foreach (TraceColumn tc in Enum.GetValues(typeof(TraceColumn)))
  876. {
  877. if ((tc.ToString() != "CurrentTime") &&
  878. (tc.ToString() != "EventClass") &&
  879. (tc.ToString() != "EventSubclass"))
  880. {
  881. string val = e[tc];
  882. if (null != val)
  883. {
  884. if (tm.Length != 0) tm.Append(", ");
  885. string v = tc + ": " + RemoveControlChars(val);
  886. tm.Append(v);
  887. }
  888. }
  889. }
  890. // Note: For text, output nothing for 'Result' since it is alwasy blank for trace events
  891. Trace(tm.ToString()); // write trace line
  892. break;
  893. }
  894. case "csv": // a csv interpreation of the event
  895. {
  896. StringBuilder tm = new StringBuilder();
  897. tm.Append(e.CurrentTime.ToString(CultureInfo.CurrentCulture));
  898. tm.Append(TraceDelim + e.EventClass.ToString());
  899. tm.Append(TraceDelim + e.EventSubclass.ToString());
  900. foreach (TraceColumn tc in Enum.GetValues(typeof(TraceColumn)))
  901. {
  902. if ((tc.ToString() != "CurrentTime") &&
  903. (tc.ToString() != "EventClass") &&
  904. (tc.ToString() != "EventSubclass"))
  905. {
  906. string val = e[tc];
  907. if (tm.Length != 0) tm.Append(TraceDelim);
  908. if (null != val) tm.Append(RemoveControlChars(val));
  909. }
  910. }
  911. tm.Append(TraceDelim); // For csv, 'Result' is always blank for trace events
  912. Trace(tm.ToString()); // write trace line
  913. break;
  914. }
  915. }
  916. TraceTimeoutCount = TraceTimeoutCountReset; // reset the no activity timer
  917. }
  918. private static void DurationTrace(Stopwatch sw, string Result)
  919. {
  920. string now = DateTime.Now.ToString(CultureInfo.CurrentCulture);
  921. string Duration = sw.ElapsedMilliseconds.ToString(CultureInfo.CurrentCulture);
  922. switch (TraceFormat)
  923. {
  924. case "text":
  925. {
  926. StringBuilder tm = new StringBuilder(); // trace message
  927. tm.Append(now);
  928. tm.Append(", Duration: " + Duration);
  929. tm.Append(", DatabaseName: " + Database);
  930. tm.Append(", TextData: " + RemoveControlChars(sb.ToString()));
  931. tm.Append(", ServerName: " + Server);
  932. if (TraceLevel == "duration-result") tm.Append(", Result: " + Result);
  933. Trace(tm.ToString()); // write trace line
  934. break;
  935. }
  936. case "csv": // a csv interpreation of the event
  937. {
  938. StringBuilder tm = new StringBuilder();
  939. tm.Append(now);
  940. tm.Append(TraceDelim); // EventClass
  941. tm.Append(TraceDelim); // EventSubclass
  942. foreach (TraceColumn tc in Enum.GetValues(typeof(TraceColumn)))
  943. {
  944. if ((tc.ToString() != "CurrentTime") &&
  945. (tc.ToString() != "EventClass") &&
  946. (tc.ToString() != "EventSubclass"))
  947. {
  948. switch (tc.ToString())
  949. {
  950. case "Duration":
  951. {
  952. tm.Append(TraceDelim);
  953. tm.Append(Duration);
  954. break;
  955. }
  956. case "DatabaseName":
  957. {
  958. tm.Append(TraceDelim);
  959. tm.Append(Database);
  960. break;
  961. }
  962. case "TextData":
  963. {
  964. tm.Append(TraceDelim);
  965. string val = sb.ToString();
  966. tm.Append(RemoveControlChars(val));
  967. break;
  968. }
  969. case "ServerName":
  970. {
  971. tm.Append(TraceDelim);
  972. tm.Append(Server);
  973. break;
  974. }
  975. default:
  976. {
  977. tm.Append(TraceDelim);
  978. break;
  979. }
  980. }
  981. }
  982. }
  983. tm.Append(TraceDelim);
  984. if (TraceLevel == "duration-result") tm.Append(Result);
  985. Trace(tm.ToString()); // write trace line
  986. break;
  987. }
  988. }
  989. }
  990. // --------------------------------------------------------------------
  991. // Supporting routines -- to generate lines in the "Output and Trace files
  992. private static void Output(Stopwatch sw, DateTime StartTime, DateTime EndTime, string msg)
  993. {
  994. if (Option_o_specified)
  995. {
  996. // Add support for NUL device
  997. if (!(OutputFile.Equals("NUL", StringComparison.CurrentCultureIgnoreCase)) &&
  998. !(OutputFile.StartsWith("NUL:", StringComparison.CurrentCultureIgnoreCase)))
  999. {
  1000. // file open delayed until absolutely needed (otherwise we end up overwriting it
  1001. // unnecessarily
  1002. if (!IsOutputFileOpen)
  1003. {
  1004. // open the file
  1005. try
  1006. {
  1007. sw_out = new StreamWriter(OutputFile, false, System.Text.Encoding.UTF8); // does an overwrite
  1008. }
  1009. catch (Exception ex)
  1010. {
  1011. Console.WriteLine(Msg(Properties.Resources.locOutputFileOpenErr), ex.Message);
  1012. throw;
  1013. }
  1014. IsOutputFileOpen = true;
  1015. }
  1016. sw_out.WriteLine(msg);
  1017. sw_out.Flush();
  1018. }
  1019. }
  1020. else
  1021. {
  1022. Console.WriteLine(msg);
  1023. }
  1024. if ((OutputResultStatFile.Length > 0) && sw != null)
  1025. {
  1026. // Add support for NUL device
  1027. if (!(OutputResultStatFile.Equals("NUL", StringComparison.CurrentCultureIgnoreCase)) &&
  1028. !(OutputResultStatFile.StartsWith("NUL:", StringComparison.CurrentCultureIgnoreCase)))
  1029. {
  1030. // file open delayed until absolutely needed (otherwise we end up overwriting it
  1031. // unnecessarily
  1032. if (!IsOutputResultStatFileOpen)
  1033. {
  1034. // open the file
  1035. try
  1036. {
  1037. sw_outResultStat = new StreamWriter(OutputResultStatFile, true, System.Text.Encoding.UTF8); // does an overwrite
  1038. }
  1039. catch (Exception ex)
  1040. {
  1041. Console.WriteLine(Msg(Properties.Resources.locOutputFileOpenErr), ex.Message);
  1042. throw;
  1043. }
  1044. IsOutputResultStatFileOpen = true;
  1045. }
  1046. OutputResultStatInfoToFile(sw, StartTime, EndTime, msg);
  1047. }
  1048. }
  1049. }
  1050. // Output the result statistics to the output stream.
  1051. // The format is CSV.
  1052. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity")]
  1053. private static void OutputResultStatInfoToFile(Stopwatch sw, DateTime StartTime, DateTime EndTime, string xmlResultString)
  1054. {
  1055. // Try to read the server xml output.
  1056. //
  1057. // InputFile = Input file name.
  1058. // TODO: We should have more information here to match up. Also need hash of input query.
  1059. // GotError = Error parsing or error from query.
  1060. // NumCols = Number of columns, based on axis0.
  1061. // NumRows = Number of rows, based on axis1.
  1062. // NumCellsTotal = Number of cells total. Calculated from virtual space of the axes.
  1063. // NumCellsNonEmpty = Number of cells having a <Value> attribute.
  1064. // This includes error cells, and cells that the server sends that have <null> for value,
  1065. // which might be the result of calculations.
  1066. // NumCellsEmpty = Number of cells not having a <Value> element. Calculated from NumCellsTotal-NumCellsNonEmpty.
  1067. // Note that the server might send <Action> or other elements in the <Cell>.
  1068. // NumCellsError = Number of cells having an <Error> element in the <Value> element.
  1069. // First output a header line if needed.
  1070. // This is nice for CSV output to read in Excel for example.
  1071. if ((!Option_NoResultStatHeader_specified) && (sw_outResultStat.BaseStream.Length <= 0))
  1072. sw_outResultStat.WriteLine("InputFile,RunInfo,StartTime,EndTime,Duration,GotError,NumCols,NumRows,NumCellsTotal,NumCellsNonEmpty,NumCellsEmpty,NumCellsError");
  1073. StringReader strReader = new StringReader(xmlResultString);
  1074. XmlTextReader xmlreader = new XmlTextReader(strReader);
  1075. bool fGotError = false;
  1076. bool fGotQueryError = false;
  1077. bool fParsingAxisRows = false;
  1078. bool fParsingAxisCols = false;
  1079. bool fParsingAxisPags = false;
  1080. bool fParsingCell = false;
  1081. bool fParsingValue = false;
  1082. int NumCols = -1;
  1083. int NumRows = -1;
  1084. int NumPags = -1;
  1085. int NumCellsValue = 0;
  1086. int NumCellsError = 0;
  1087. try
  1088. {
  1089. // Quickly skip over info at the beginning we don't currently use.
  1090. // No, must be able to recognize entire query error, which is <Exception>.
  1091. //xmlreader.ReadToFollowing("Axes");
  1092. while (xmlreader.Read() && !fGotError)
  1093. {
  1094. switch (xmlreader.NodeType)
  1095. {
  1096. case XmlNodeType.Element:
  1097. switch (xmlreader.Name)
  1098. {
  1099. case "Exception":
  1100. // This is an error in the mdx query.
  1101. // <Exception xmlns="urn:schemas-microsoft-com:xml-analysis:exception"></Exception>
  1102. // <Messages xmlns="urn:schemas-microsoft-com:xml-analysis:exception">
  1103. // <Error ErrorCode="3238658057" Description="Query (19, 1) The member &apos;[Z_TY Str Stktrn]&apos; was not found in the cube when the string, [Measures].[Z_TY Str Stktrn], was parsed." Source="Microsoft SQL Server 2005 Analysis Services" HelpFile=""></Error>
  1104. // </Messages>
  1105. fGotQueryError = true;
  1106. break;
  1107. case "Axis":
  1108. fParsingAxisRows = false;
  1109. fParsingAxisCols = false;
  1110. fParsingAxisPags = false;
  1111. xmlreader.MoveToFirstAttribute();
  1112. if (xmlreader.Name != "name")
  1113. {
  1114. // todo: Need to use Properties.Resources for error messages.
  1115. Console.WriteLine("Warning: Expecting result 'Axis' element to have 'name' attribute");
  1116. fGotError = true;
  1117. break;
  1118. }
  1119. if (xmlreader.Value == "Axis0")
  1120. {
  1121. fParsingAxisCols = true;
  1122. NumCols = 0;
  1123. }
  1124. else if (xmlreader.Value == "Axis1")
  1125. {
  1126. fParsingAxisRows = true;
  1127. NumRows = 0;
  1128. }
  1129. else if (xmlreader.Value == "Axis2")
  1130. {
  1131. fParsingAxisPags = true;
  1132. NumPags = 0;
  1133. }
  1134. //else if (xmlreader.Value == "SlicerAxis")
  1135. break;
  1136. case "Tuple":
  1137. if (fParsingAxisCols)
  1138. NumCols++;
  1139. else if (fParsingAxisRows)
  1140. NumRows++;
  1141. else if (fParsingAxisPags)
  1142. NumPags++;
  1143. // Skip children of this node (for speed).
  1144. // No, cannot use, this causes it to skip other "tuple" siblings.
  1145. //xmlreader.Skip();
  1146. break;
  1147. case "Cell":
  1148. // Note that we consider the case where actions are specified,
  1149. // but there is no <value> node underneath the <cell> node.
  1150. //
  1151. // Here is an example of a cell we will not count as NumCellsNonEmpty because it is missing <Value>.
  1152. // <Cell CellOrdinal="18">
  1153. // <ActionType>0</ActionType>
  1154. // </Cell>
  1155. //
  1156. // Here is an example of a cell error:
  1157. // <Cell CellOrdinal="8052">
  1158. // <Value>
  1159. // <Error>
  1160. // <ErrorCode>3238658057</ErrorCode>
  1161. // <Description>MdxScript(Edgars) (6267, 1) The member '[53 Weeks Ave Str Stk]' was not found in the cube when the string, [Measures].[53 Weeks Ave Str Stk], was parsed.</Description>
  1162. // </Error>
  1163. // </Value>
  1164. fParsingCell = true;
  1165. break;
  1166. // Note that we do not care about the Cell Ordinal, only the number of cells we see.
  1167. /*
  1168. if (!xmlreader.HasAttributes)
  1169. {
  1170. // Need to use Properties.Resources for error messages.
  1171. Console.WriteLine("Warning: Expecting result 'Cell' element to have 'CellOrdinal' attribute but it is missing.");
  1172. fGotError = true;
  1173. break;
  1174. }
  1175. xmlreader.MoveToFirstAttribute();
  1176. if (xmlreader.Name != "CellOrdinal")
  1177. {
  1178. // Need to use Properties.Resources for error messages.
  1179. Console.WriteLine("Warning: Expecting result 'Cell' element to have 'CellOrdinal' attribute");
  1180. fGotError = true;
  1181. break;
  1182. }
  1183. int iCellOrdinal;
  1184. if (!int.TryParse(xmlreader.Value, out iCellOrdinal))
  1185. {
  1186. // Need to use Properties.Resources for error messages.
  1187. Console.WriteLine("Warning: Expecting result 'Cell' element to have 'CellOrdinal' attribute with number, found '{0}'", xmlreader.Value);
  1188. fGotError = true;
  1189. break;
  1190. }
  1191. NumCells = Math.Max(NumCells, iCellOrdinal + 1);
  1192. break;
  1193. */
  1194. case "Value":
  1195. if (fParsingCell)
  1196. {
  1197. fParsingValue = true;
  1198. NumCellsValue++;
  1199. }
  1200. break;
  1201. case "Error":
  1202. if (fParsingCell && fParsingValue)
  1203. {
  1204. // Note that <Error> is inside <Value>.
  1205. // Therefore NumCellsValue includes NumCellsError.
  1206. NumCellsError++;
  1207. }
  1208. break;
  1209. }
  1210. break;
  1211. case XmlNodeType.EndElement:
  1212. switch (xmlreader.Name)
  1213. {
  1214. case "Cell":
  1215. fParsingCell = false;
  1216. break;
  1217. case "Value":
  1218. fParsingValue = false;
  1219. break;
  1220. }
  1221. break;
  1222. case XmlNodeType.Text:
  1223. case XmlNodeType.CDATA:
  1224. case XmlNodeType.Comment:
  1225. case XmlNodeType.XmlDeclaration:
  1226. case XmlNodeType.Document:
  1227. case XmlNodeType.DocumentType:
  1228. case XmlNodeType.EntityReference:
  1229. case XmlNodeType.Whitespace:
  1230. default:
  1231. break;
  1232. }
  1233. }
  1234. // Replace the contents only if we were entirely successful.
  1235. if (xmlreader.EOF)
  1236. {
  1237. // Note that we handle only up to 3 dimensions here.
  1238. // Some UI tools internally use 3 dimensions.
  1239. // We set each to -1 to begin with, and set to 0 when first obtained.
  1240. // so -1 means axis is not present, and 0 means axis is present with 0 tuples.
  1241. // For example, if query does Non Empty on rows and there are 0, then NumCellsTotal = 0.
  1242. int NumCellsTotal = 0;
  1243. if (NumCols >= 0)
  1244. NumCellsTotal = NumCols;
  1245. if (NumRows >= 0)
  1246. NumCellsTotal *= NumRows;
  1247. if (NumPags >= 0)
  1248. NumCellsTotal *= NumPags;
  1249. int NumCellsEmpty = NumCellsTotal - NumCellsValue;
  1250. // InputFile,RunInfo,StartTime,EndTime,Duration,GotError,NumCols,NumRows,NumCellsTotal,NumCellsNonEmpty,NumCellsEmpty,NumCellsError
  1251. sw_outResultStat.WriteLine("{0},{1},{2},{3},{4:#.000},{5},{6},{7},{8},{9},{10},{11}",
  1252. InputFile,
  1253. RunInfo.ToString(CultureInfo.CurrentCulture),
  1254. StartTime.ToString("s", CultureInfo.CurrentCulture),
  1255. EndTime.ToString("s", CultureInfo.CurrentCulture),
  1256. sw.Elapsed.TotalSeconds,
  1257. fGotQueryError ? 1 : 0,
  1258. NumCols,
  1259. NumRows,
  1260. NumCellsTotal,
  1261. NumCellsValue, // NumCellsNonEmpty
  1262. NumCellsEmpty,
  1263. NumCellsError);
  1264. sw_outResultStat.Flush();
  1265. return;
  1266. }
  1267. }
  1268. catch (System.Xml.XmlException ex)
  1269. {
  1270. //@todo: Do we need another error message, or re-use this one?
  1271. Console.WriteLine(Msg(Properties.Resources.locFailedErr), ex.Message);
  1272. // We ignore exceptions from XML.
  1273. //throw;
  1274. }
  1275. catch (Exception ex)
  1276. {
  1277. //@todo: Do we need another error message, or re-use this one?
  1278. Console.WriteLine(Msg(Properties.Resources.locFailedErr), ex.Message);
  1279. throw;
  1280. }
  1281. sw_outResultStat.WriteLine("-1,-1,-1,-1");
  1282. return;
  1283. }
  1284. private static void Trace(string msg)
  1285. {
  1286. if (!IsTraceFileOpen)
  1287. {
  1288. bool appendFlag;
  1289. // file open delayed until absolutely needed
  1290. try
  1291. {
  1292. // if doing duration trace and file already exists, then don't overwrite
  1293. // but instead do an append
  1294. appendFlag = (((TraceLevel == "duration") || (TraceLevel == "duration-result")) &&
  1295. File.Exists(TraceFile)) ? true : false;
  1296. sw_trace = new StreamWriter(TraceFile, appendFlag, System.Text.Encoding.UTF8);
  1297. }
  1298. catch (Exception ex)
  1299. {
  1300. Console.WriteLine(Msg(Properties.Resources.locTraceFileOpenErr), ex.Message);
  1301. throw;
  1302. }
  1303. IsTraceFileOpen = true;
  1304. // Ok Trace file is now ready to go
  1305. // If csv trace file format and we are not appending to the end, then
  1306. // output the header line in the trace
  1307. if ((TraceFormat == "csv") && !appendFlag)
  1308. {
  1309. StringBuilder tm = new StringBuilder();
  1310. tm.Append("CurrentTime" + TraceDelim +
  1311. "EventClass" + TraceDelim + "EventSubclass");
  1312. foreach (TraceColumn tc in Enum.GetValues(typeof(TraceColumn)))
  1313. {
  1314. if ((tc.ToString() != "CurrentTime") &&
  1315. (tc.ToString() != "EventClass") &&
  1316. (tc.ToString() != "EventSubclass"))
  1317. {
  1318. tm.Append(TraceDelim);
  1319. tm.Append(tc);
  1320. }
  1321. }
  1322. tm.Append(TraceDelim + "Result");
  1323. Trace(tm.ToString());
  1324. }
  1325. }
  1326. // write out the trace line
  1327. try
  1328. {
  1329. sw_trace.WriteLine(msg);
  1330. }
  1331. catch (Exception ex)
  1332. {
  1333. Console.WriteLine(Msg(Properties.Resources.locTraceWriteErr), ex.Message);
  1334. throw;
  1335. }
  1336. }
  1337. // --------------------------------------------------------------------
  1338. // Supporting routines -- close all files
  1339. private static void CloseFiles()
  1340. {
  1341. // OK, we are all done. Perform some file cleanup and exit.
  1342. if (IsOutputFileOpen) CloseFile(sw_out, Msg(Properties.Resources.locOutputFileCloseErr));
  1343. if (IsTraceFileOpen) CloseFile(sw_trace, Msg(Properties.Resources.locTraceFileCloseErr));
  1344. }
  1345. private static void CloseFile(StreamWriter sw, string errorMsg)
  1346. {
  1347. try
  1348. {
  1349. sw.Close();
  1350. }
  1351. catch (Exception ex)
  1352. {
  1353. Console.WriteLine(errorMsg, ex.Message);
  1354. throw;
  1355. }
  1356. }
  1357. // --------------------------------------------------------------------
  1358. // Supporting routine -- to parse the command line
  1359. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity")]
  1360. private static bool ParseArgs(string[] args)
  1361. {
  1362. if (args.Length == 0) // nothing on the command-line
  1363. {
  1364. // as a minimum -i or -Q must be specified
  1365. Console.WriteLine(Msg(Properties.Resources.locInpEitherErr));
  1366. ShowHelp(); return false;
  1367. }
  1368. GetScriptingVariables(); // Get scripting variables from the OS
  1369. // Now loop through the command-line args
  1370. for (int i = 0; i < args.Length; i++)
  1371. {
  1372. string arg = args[i].ToString(CultureInfo.CurrentCulture);
  1373. if (_DEBUGMODE) Console.WriteLine(arg);
  1374. switch (arg)
  1375. {
  1376. case "/?": // Unix way of requesting help
  1377. case "-?": // and this is the Windows way :-)
  1378. {
  1379. ShowHelp();
  1380. return false;
  1381. }
  1382. case "-U": // User Id
  1383. {
  1384. if (Option_U_specified) return OptionSeen("-U");
  1385. Option_U_specified = true;
  1386. if (i == (args.Length - 1)) return MissingArg("-U"); // option must have an argument
  1387. UserName = args[++i].ToString(CultureInfo.CurrentCulture);
  1388. if (_DEBUGMODE) Console.WriteLine("*" + UserName);
  1389. break;
  1390. }
  1391. case "-P": // Password
  1392. {
  1393. if (Option_P_specified) return OptionSeen("-P");
  1394. Option_P_specified = true;
  1395. if (i == (args.Length - 1)) return MissingArg("-P"); // option must have an argument
  1396. Password = args[++i].ToString(CultureInfo.CurrentCulture);
  1397. if (_DEBUGMODE) Console.WriteLine("*" + Password);
  1398. break;
  1399. }
  1400. case "-S": // Server
  1401. {
  1402. if (Option_S_specified) return OptionSeen("-S");
  1403. Option_S_specified = true;
  1404. if (i == (args.Length - 1)) return MissingArg("-S"); // option must have an argument
  1405. Server = args[++i].ToString(CultureInfo.CurrentCulture);
  1406. if (_DEBUGMODE) Console.WriteLine("*" + Server);
  1407. break;
  1408. }
  1409. case "-d": // Database
  1410. {
  1411. if (Option_d_specified) return OptionSeen("-d");
  1412. Option_d_specified = true;
  1413. if (i == (args.Length - 1)) return MissingArg("-d"); // option must have an argument
  1414. Database = args[++i].ToString(CultureInfo.CurrentCulture);
  1415. if (_DEBUGMODE) Console.WriteLine("*" + Database);
  1416. break;
  1417. }
  1418. case "-t": // Timeout (for query)
  1419. {
  1420. if (Option_t_specified) return OptionSeen("-t");
  1421. Option_t_specified = true;
  1422. if (i == (args.Length - 1)) return MissingArg("-t"); // option must have an argument
  1423. Timeout = args[++i].ToString(CultureInfo.CurrentCulture);
  1424. if (_DEBUGMODE) Console.WriteLine("*" + Timeout.ToString(CultureInfo.CurrentCulture));
  1425. break;
  1426. }
  1427. case "-tc": // Timeout (for connections)
  1428. {
  1429. if (Option_tc_specified) return OptionSeen("-tc");
  1430. Option_tc_specified = true;
  1431. if (i == (args.Length - 1)) return MissingArg("-tc"); // option must have an argument
  1432. ConnectTimeout = args[++i].ToString(CultureInfo.CurrentCulture);
  1433. if (_DEBUGMODE) Console.WriteLine("*" + ConnectTimeout.ToString(CultureInfo.CurrentCulture));
  1434. break;
  1435. }
  1436. case "-i": // InputFile
  1437. {
  1438. // Allow multiple input files.
  1439. //if (Option_i_specified) return OptionSeen("-i");
  1440. Option_i_specified = true;
  1441. if (i == (args.Length - 1)) return MissingArg("-i"); // option must have an argument
  1442. InputFile = args[++i].ToString(CultureInfo.CurrentCulture);
  1443. if (_DEBUGMODE) Console.WriteLine("*" + InputFile);
  1444. // Validate the file exists
  1445. if (File.Exists(InputFile))
  1446. {
  1447. // Read in the query or script from the input file
  1448. try
  1449. {
  1450. // Force the reader to be UTF8 and to look for byte-order marker in file
  1451. StreamReader sr = new StreamReader(InputFile, System.Text.Encoding.UTF8, true);
  1452. if (sb.Length == 0)
  1453. {
  1454. sb.AppendFormat(InputFileFormat,InputFile.ToString());
  1455. sb.Append(sr.ReadToEnd().Trim());
  1456. TryParseAsMultiUserXML(ref sb);
  1457. }
  1458. else
  1459. {
  1460. StringBuilder sbTmp = new StringBuilder(sr.ReadToEnd().Trim());
  1461. TryParseAsMultiUserXML(ref sbTmp);
  1462. sb.Append("\ngo\n");
  1463. sb.AppendFormat("**InputFile: {0}\n",InputFile.ToString());
  1464. sb.Append(sbTmp);
  1465. }
  1466. }
  1467. catch (Exception ex)
  1468. {
  1469. Console.WriteLine(Msg(Properties.Resources.locInpFileGeneralErr), InputFile, ex.Message);
  1470. throw;
  1471. }
  1472. // If input file is empty, return an error.
  1473. if (sb.Length == 0)
  1474. {
  1475. Console.WriteLine(Msg(Properties.Resources.locInpFileEmptyErr), InputFile);
  1476. return false;
  1477. }
  1478. }
  1479. else
  1480. {
  1481. // Input file does not exist
  1482. Console.WriteLine(Msg(Properties.Resources.locInpFileNotExistErr), InputFile);
  1483. return false;
  1484. }
  1485. break;
  1486. }
  1487. case "-o": // OutputFile
  1488. {
  1489. if (Option_o_specified) return OptionSeen("-o");
  1490. Option_o_specified = true;
  1491. if (i == (args.Length - 1)) return MissingArg("-o"); // option must have an argument
  1492. OutputFile = args[++i].ToString(CultureInfo.CurrentCulture);
  1493. if (_DEBUGMODE) Console.WriteLine("*" + OutputFile);
  1494. // Make sure the output file name format is OK
  1495. if (!CheckValidChars(Properties.Resources.locNameOutputFile,
  1496. 1, -1, "", OutputFile)) return false;
  1497. break;
  1498. }
  1499. case "-oResultStat": // OutputFile for result statistics
  1500. {
  1501. if (Option_oResultStat_specified) return OptionSeen("-oResultStat");
  1502. Option_oResultStat_specified = true;
  1503. if (i == (args.Length - 1)) return MissingArg("-oResultStat"); // option must have an argument
  1504. OutputResultStatFile = args[++i].ToString(CultureInfo.CurrentCulture);
  1505. if (_DEBUGMODE) Console.WriteLine("*" + OutputResultStatFile);
  1506. // Make sure the output file name format is OK
  1507. if (!CheckValidChars(Properties.Resources.locNameOutputResultStatFile,
  1508. 1, -1, "", OutputResultStatFile)) return false;
  1509. break;
  1510. }
  1511. case "-NoResultStatHeader":
  1512. {
  1513. // no argument
  1514. if (Option_NoResultStatHeader_specified) return OptionSeen("-NoResultStatHeader");
  1515. Option_NoResultStatHeader_specified = true;
  1516. break;
  1517. }
  1518. case "-RunInfo": // run information to be copied into the Result Stat file
  1519. {
  1520. if (Option_RunInfo_specified) return OptionSeen("-RunInfo");
  1521. Option_RunInfo_specified = true;
  1522. if (i == (args.Length - 1)) return MissingArg("-RunInfo"); // option must have an argument
  1523. RunInfo = args[++i].ToString(CultureInfo.CurrentCulture);
  1524. if (_DEBUGMODE) Console.WriteLine("*" + RunInfo.ToString(CultureInfo.CurrentCulture));
  1525. // No checks made -- whatever you enter will be placed in the run info sttring
  1526. break;
  1527. }
  1528. case "-RandomSeed":
  1529. {
  1530. if (!ParseIntegerArg(args, ref i, "-RandomSeed", ref Option_RandomSeed_specified, ref RandomSeed))
  1531. return false;
  1532. break;
  1533. }
  1534. case "-ThinkTimeMin":
  1535. {
  1536. if (!ParseIntegerArg(args, ref i, "-ThinkTimeMin", ref Option_ThinkTimeMin_specified, ref ThinkTimeMin))
  1537. return false;
  1538. break;
  1539. }
  1540. case "-ThinkTimeMax":
  1541. {
  1542. if (!ParseIntegerArg(args, ref i, "-ThinkTimeMax", ref Option_ThinkTimeMax_specified, ref ThinkTimeMax))
  1543. return false;
  1544. break;
  1545. }
  1546. case "-ConnectWaitMin":
  1547. {
  1548. if (!ParseIntegerArg(args, ref i, "-ConnectWaitMin", ref Option_ConnectWaitMin_specified, ref ConnectWaitMin))
  1549. return false;
  1550. break;
  1551. }
  1552. case "-ConnectWaitMax":
  1553. {
  1554. if (!ParseIntegerArg(args, ref i, "-ConnectWaitMax", ref Option_ConnectWaitMax_specified, ref ConnectWaitMax))
  1555. return false;
  1556. break;
  1557. }
  1558. case "-Xf":
  1559. {
  1560. if (Option_Xf_specified) return OptionSeen("-Xf");
  1561. Option_Xf_specified = true;
  1562. if (i == (args.Length - 1)) return MissingArg("-Xf"); // option must have an argument
  1563. ExitFile = args[++i].ToString(CultureInfo.CurrentCulture);
  1564. if (_DEBUGMODE) Console.WriteLine("*" + ExitFile.ToString(CultureInfo.CurrentCulture));
  1565. // If the exit file already exists, then generate an error
  1566. if (File.Exists(ExitFile))
  1567. {
  1568. Console.WriteLine(Msg(Properties.Resources.locExitFileExistsErr), ExitFile);
  1569. return false;
  1570. }
  1571. break;
  1572. }
  1573. case "-Q": // command line query
  1574. {
  1575. if (Option_Q_specified) return OptionSeen("-Q");
  1576. Option_Q_specified = true;
  1577. if (i == (args.Length - 1)) return MissingArg("-Q"); // option must have an argument
  1578. Query = args[++i].ToString(CultureInfo.CurrentCulture);
  1579. if (_DEBUGMODE) Console.WriteLine("*" + "\"" + Query + "\"");
  1580. // If input file is empty, return an error.
  1581. if (Query.Length == 0)
  1582. {
  1583. Console.WriteLine(Msg(Properties.Resources.locOptionQEmptyErr));
  1584. return false;
  1585. }
  1586. else
  1587. {
  1588. sb.Append(Query.Trim());
  1589. }
  1590. break;
  1591. }
  1592. case "-T": // TraceFile
  1593. {
  1594. if (Option_T_specified) return OptionSeen("-T");
  1595. Option_T_specified = true;
  1596. if (i == (args.Length - 1)) return MissingArg("-T"); // option must have an argument
  1597. TraceFile = args[++i].ToString(CultureInfo.CurrentCulture);
  1598. if (_DEBUGMODE) Console.WriteLine("*" + TraceFile);
  1599. // Make sure the trace file name format is OK
  1600. if (!CheckValidChars(Properties.Resources.locNameTraceFile,
  1601. 1, -1, "", TraceFile)) return false;
  1602. break;
  1603. }
  1604. case "-Tf": // Trace file format (only valid if -T is also specified)
  1605. {
  1606. if (Option_Tf_specified) return OptionSeen("-Tf");
  1607. Option_Tf_specified = true;
  1608. if (i == (args.Length - 1)) return MissingArg("-Tf"); // option must have an argument
  1609. TraceFormat = args[++i].ToString(CultureInfo.CurrentCulture);
  1610. if (_DEBUGMODE) Console.WriteLine("*" + TraceFormat.ToString(CultureInfo.CurrentCulture));
  1611. break;
  1612. }
  1613. case "-Tl": // Trace file level (only valid if -T is also specified)
  1614. {
  1615. if (Option_Tl_specified) return OptionSeen("-Tl");
  1616. Option_Tl_specified = true;
  1617. if (i == (args.Length - 1)) return MissingArg("-Tl"); // option must have an argument
  1618. TraceLevel = args[++i].ToString(CultureInfo.CurrentCulture);
  1619. if (_DEBUGMODE) Console.WriteLine("*" + TraceLevel.ToString(CultureInfo.CurrentCulture));
  1620. break;
  1621. }
  1622. case "-Td": // Trace file delimiter (default is "|", vertical bar or pipe)
  1623. {
  1624. if (Option_Td_specified) return OptionSeen("-Td");
  1625. Option_Td_specified = true;
  1626. if (i == (args.Length - 1)) return MissingArg("-Td"); // option must have an argument
  1627. TraceDelim = args[++i].ToString(CultureInfo.CurrentCulture);
  1628. if (_DEBUGMODE) Console.WriteLine("*" + ExtendedConnectstring.ToString(CultureInfo.CurrentCulture));
  1629. break;
  1630. }
  1631. case "-Tt": // Timeout (for trace) -- indicates the amount of inactivity that signals trace is complete
  1632. {
  1633. if (Option_Tt_specifed) return OptionSeen("-Tt");
  1634. Option_Tt_specifed = true;
  1635. if (i == (args.Length - 1)) return MissingArg("-Tt"); // option must have an argument
  1636. TraceTimeout = args[++i].ToString(CultureInfo.CurrentCulture);
  1637. if (_DEBUGMODE) Console.WriteLine("*" + TraceTimeout.ToString(CultureInfo.CurrentCulture));
  1638. break;
  1639. }
  1640. case "-xc": // Extended connectstring
  1641. {
  1642. if (Option_xc_specified) return OptionSeen("-xc");
  1643. Option_xc_specified = true;
  1644. if (i == (args.Length - 1)) return MissingArg("-xc"); // option must have an argument
  1645. ExtendedConnectstring = args[++i].ToString(CultureInfo.CurrentCulture);
  1646. if (_DEBUGMODE) Console.WriteLine("*" + ExtendedConnectstring.ToString(CultureInfo.CurrentCulture));
  1647. // No checks made -- whatever you enter will be placed on the connect sttring
  1648. break;
  1649. }
  1650. case "-v": // Set scripting variables
  1651. {
  1652. if (Option_v_specified) return OptionSeen("-v");
  1653. Option_v_specified = true;
  1654. // -v options are a bit different.
  1655. // They are in the form '-v name=value name=value name=value'
  1656. // Loop through arguments until we no longer get a name=value pair
  1657. while (i < (args.Length - 1)) // don't loop off the end
  1658. {
  1659. string Entry = args[++i].ToString(CultureInfo.CurrentCulture);
  1660. // look to see if the arg is name=value where name is a
  1661. // valid scripting variable
  1662. Regex expression = new Regex(ScriptingVarNameRegex);
  1663. if (!expression.IsMatch(Entry))
  1664. {
  1665. i -= 1; // Nope, backup and retry the argument as an option
  1666. break;
  1667. }
  1668. else
  1669. {
  1670. // Ok if we got here then this is a -v entry
  1671. // Parse it into its name=value pair
  1672. string[] ParsedEntry = Entry.Split(new Char[] { '=' }, 2);
  1673. string name = ParsedEntry[0];
  1674. string value = ParsedEntry[1];
  1675. if (_DEBUGMODE) Console.WriteLine("-v Name:*" + name);
  1676. if (_DEBUGMODE) Console.WriteLine("-v Value:*" + value);
  1677. // Validate the name -- it must not start with 'ASCMD'
  1678. // that is reservered for command-line options
  1679. if (name.StartsWith("ASCMD",
  1680. StringComparison.CurrentCultureIgnoreCase))
  1681. {
  1682. Console.WriteLine(Msg(Properties.Resources.locScriptingVarInvalidNameErr), Entry);
  1683. return false;
  1684. }
  1685. else
  1686. {
  1687. // add it to substitutionWith table
  1688. if (!substituteWith.ContainsKey(name))
  1689. {
  1690. // Entry is not already known, add it
  1691. substituteWith.Add(name, value);
  1692. }
  1693. else
  1694. {
  1695. // Entry already there, override it
  1696. // Note: this means that if a name is specified
  1697. // more than once, then the last name=value pair
  1698. // on the command line wins
  1699. substituteWith[name] = value;
  1700. }
  1701. }
  1702. }
  1703. }
  1704. break;
  1705. }
  1706. case "-DEBUG": // set debug mode
  1707. {
  1708. // no argument
  1709. _DEBUGMODE = true;
  1710. break;
  1711. }
  1712. default: // error
  1713. {
  1714. Console.WriteLine(Msg(Properties.Resources.locUnknownOptionErr), arg);
  1715. return false;
  1716. }
  1717. }
  1718. }
  1719. // Verify the current settings -- returns false if an error found
  1720. if (!ValidateSettings()) return false;
  1721. // Verify input stream
  1722. // 1) command-line must have either -i or -Q, but not both
  1723. // 2) there must be something to actually execute (some script or
  1724. // query)
  1725. if (Option_i_specified && Option_Q_specified)
  1726. { // both
  1727. Console.WriteLine(Msg(Properties.Resources.locInpNotBothErr));
  1728. return false;
  1729. }
  1730. else if (!(Option_i_specified || Option_Q_specified))
  1731. { // either -i or -Q must be present
  1732. Console.WriteLine(Msg(Properties.Resources.locInpEitherErr));
  1733. return false;
  1734. }
  1735. else if (sb.Length == 0)
  1736. {
  1737. // If we got here then we don't have anything to execute,
  1738. // Note: This is not really needed, because empty checking is
  1739. // also in both the -Q and -i option parsing
  1740. Console.WriteLine(Msg(Properties.Resources.locNoInpErr)); //
  1741. return false;
  1742. }
  1743. // You cannot run trace over an http or https connection
  1744. if (Option_T_specified && httpConnection)
  1745. {
  1746. Console.WriteLine(Msg(Properties.Resources.locTraceNoHttpErr));
  1747. return false;
  1748. }
  1749. // Set scripting variables
  1750. SetScriptingVariables();
  1751. return true; // All of the options are parsed and files are open ready to go
  1752. }
  1753. // --------------------------------------------------------------------
  1754. // Helper routines for ParseArgs
  1755. private static bool ParseIntegerArg(string[] args, ref int iArg, string OptionString, ref bool OptionSpecified, ref int OptionVal)
  1756. {
  1757. if (OptionSpecified)
  1758. return OptionSeen(OptionString);
  1759. OptionSpecified = true;
  1760. if (iArg == (args.Length - 1))
  1761. return MissingArg(OptionString); // option must have an argument
  1762. iArg++;
  1763. if (!Int32.TryParse(args[iArg].ToString((CultureInfo.CurrentCulture)), out OptionVal))
  1764. {
  1765. Console.WriteLine(Msg(Properties.Resources.locIntegerErr), OptionString, args[iArg]);
  1766. return false;
  1767. }
  1768. if (_DEBUGMODE) Console.WriteLine("*" + OptionVal.ToString(CultureInfo.CurrentCulture));
  1769. return true;
  1770. }
  1771. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity")]
  1772. private static bool TryParseAsMultiUserXML( ref StringBuilder sbQuerySet )
  1773. {
  1774. // Try to read the string (which was the input file) using the Multi-User XML format.
  1775. // If we succeed, translate into a big string of queries with "GO" in between.
  1776. // Later code parses the "GO" to create an array of queries.
  1777. //
  1778. // Our goal is to be permissive, and just suck up the first text under <Query> or <Statement>.
  1779. // LoadSim tool creates <Query>.
  1780. // Hopper tool uses <Statement>.
  1781. StringReader strReader = new StringReader(sbQuerySet.ToString());
  1782. XmlTextReader xmlreader = new XmlTextReader(strReader);
  1783. StringBuilder sbCandidateQuerySet = new StringBuilder();
  1784. bool fGotError = false;
  1785. bool fInQuery = false;
  1786. try
  1787. {
  1788. while (xmlreader.Read() && !fGotError)
  1789. {
  1790. switch (xmlreader.NodeType)
  1791. {
  1792. case XmlNodeType.Element:
  1793. if (xmlreader.Name == "Query" || xmlreader.Name == "Statement")
  1794. fInQuery = true;
  1795. break;
  1796. case XmlNodeType.EndElement:
  1797. fInQuery = false;
  1798. break;
  1799. case XmlNodeType.Text:
  1800. if (fInQuery)
  1801. {
  1802. string strQuery = xmlreader.Value.Trim();
  1803. if (strQuery.Length > 0)
  1804. {
  1805. if (sbCandidateQuerySet.Length > 0)
  1806. sbCandidateQuerySet.AppendLine("go");
  1807. sbCandidateQuerySet.AppendLine(strQuery);
  1808. fInQuery = false;
  1809. }
  1810. }
  1811. break;
  1812. case XmlNodeType.CDATA:
  1813. case XmlNodeType.Comment:
  1814. case XmlNodeType.XmlDeclaration:
  1815. case XmlNodeType.Document:
  1816. case XmlNodeType.DocumentType:
  1817. case XmlNodeType.EntityReference:
  1818. case XmlNodeType.Whitespace:
  1819. default:
  1820. break;
  1821. }
  1822. }
  1823. // Replace the contents only if we were entirely successful.
  1824. if (xmlreader.EOF && !fGotError && sbCandidateQuerySet.Length > 0)
  1825. {
  1826. sbQuerySet = sbCandidateQuerySet;
  1827. return true;
  1828. }
  1829. }
  1830. catch (System.Xml.XmlException)
  1831. {
  1832. // We ignore exceptions from XML, because this is how they do error handling.
  1833. //System.Diagnostics.Debug.WriteLine(ex.Message);
  1834. //throw;
  1835. }
  1836. return false;
  1837. }
  1838. private static bool ValidateSettings()
  1839. {
  1840. if (!ValidateOption_S) return false; // This must be done first so the httpConnection can be set first
  1841. if (!ValidateOption_U) return false;
  1842. if (!ValidateOption_P) return false;
  1843. if (!ValidateOption_d) return false;
  1844. if (!ValidateTimeout("-t", Timeout)) return false;
  1845. if (!ValidateTimeout("-tc", ConnectTimeout)) return false;
  1846. if (!ValidateOption_Tf) return false;
  1847. if (!ValidateOption_Tl) return false;
  1848. if (!ValidateOption_Td) return false;
  1849. if (!ValidateTimeout("-Tt", TraceTimeout)) return false;
  1850. return true;
  1851. }
  1852. // -S validate
  1853. // NOTE: This must be done first so the httpConnection can be set first
  1854. private static bool ValidateOption_S
  1855. {
  1856. get
  1857. {
  1858. // Is this a http or https connection??
  1859. string s = Server.ToLower(CultureInfo.CurrentCulture);
  1860. if (s.StartsWith("http://", StringComparison.CurrentCultureIgnoreCase) ||
  1861. s.StartsWith("https://", StringComparison.CurrentCultureIgnoreCase))
  1862. {
  1863. httpConnection = true;
  1864. if (_DEBUGMODE) Console.WriteLine("Http Server:*" + Server);
  1865. if (!CheckValidChars(Properties.Resources.locNameServer,
  1866. 8, -1, "", Server)) return false;
  1867. }
  1868. else
  1869. {
  1870. // normal client-server connections -- either just the server or multi-instance
  1871. httpConnection = false;
  1872. if (Server.Contains("\\")) // backslash anywhere on line?
  1873. {
  1874. // multi-instance server name (i.e. <server>\<instance>
  1875. string[] ParsedServer = Server.Split(new Char[] { '\\' }, 2);
  1876. string partServer = ParsedServer[0];
  1877. string partInstance = ParsedServer[1];
  1878. if (_DEBUGMODE) Console.WriteLine("Server:*" + partServer);
  1879. if (_DEBUGMODE) Console.WriteLine("Instance:*" + partInstance);
  1880. // Check for valid server and instance name
  1881. // Note: the \ character not in Invalid char list since we support named instances
  1882. if (!CheckValidChars(Properties.Resources.locNameServer,
  1883. 1, -1, "", partServer)) return false;
  1884. if (!CheckValidChars(Properties.Resources.locNameInstance,
  1885. 1, -1, "", partInstance)) return false;
  1886. Server = partServer;
  1887. Instance = partInstance;
  1888. }
  1889. else
  1890. {
  1891. // just a plain old server name
  1892. if (_DEBUGMODE) Console.WriteLine("Server:*" + Server);
  1893. if (!CheckValidChars(Properties.Resources.locNameServer,
  1894. 1, -1, "", Server)) return false;
  1895. }
  1896. }
  1897. return true;
  1898. }
  1899. }
  1900. // --------------------
  1901. // -U validate
  1902. private static bool ValidateOption_U
  1903. {
  1904. get
  1905. {
  1906. if (UserName.Length > 0)
  1907. {
  1908. if (UserName.Contains("\\"))
  1909. {
  1910. string[] ParsedUserName = UserName.Split(new Char[] { '\\' }, 2);
  1911. Domain = ParsedUserName[0];
  1912. UserName = ParsedUserName[1];
  1913. }
  1914. else if (!httpConnection)
  1915. {
  1916. // For a TCP/IP connection, the username must be in the format <domain>\<username>
  1917. Console.WriteLine(Msg(Properties.Resources.locOptionUFormatErr));
  1918. return false;
  1919. }
  1920. if (!CheckValidChars(Properties.Resources.locNameUsername,
  1921. 1, -1, "", UserName)) return false;
  1922. if (!CheckValidChars(Properties.Resources.locNameDomain,
  1923. 1, -1, "", Domain)) return false;
  1924. }
  1925. return true;
  1926. }
  1927. }
  1928. // --------------------
  1929. // -P validate
  1930. private static bool ValidateOption_P
  1931. {
  1932. get
  1933. {
  1934. if (Password.Length > 0)
  1935. {
  1936. if (!CheckValidChars(Properties.Resources.locNamePassword,
  1937. 0, -1, "", Password)) return false;
  1938. }
  1939. return true;
  1940. }
  1941. }
  1942. // --------------------
  1943. // -d validate
  1944. private static bool ValidateOption_d
  1945. {
  1946. get
  1947. {
  1948. if (Database.Length > 0)
  1949. {
  1950. if (!CheckValidChars(Properties.Resources.locNameDatabase,
  1951. 1, 128, "", Database)) return false;
  1952. }
  1953. return true;
  1954. }
  1955. }
  1956. // --------------------
  1957. // -Tf validate
  1958. private static bool ValidateOption_Tf
  1959. {
  1960. get
  1961. {
  1962. // only 2 supported options -- validate their names here
  1963. TraceFormat = TraceFormat.ToLower(CultureInfo.CurrentCulture);
  1964. if ((TraceFormat != "text") && (TraceFormat != "csv"))
  1965. {
  1966. Console.WriteLine(Msg(Properties.Resources.locTraceInvalidFormatErr), TraceFormat);
  1967. return false;
  1968. }
  1969. return true;
  1970. }
  1971. }
  1972. // --------------------
  1973. // -Tl validate
  1974. private static bool ValidateOption_Tl
  1975. {
  1976. get
  1977. {
  1978. // only 4 supported options -- validate their names here
  1979. TraceLevel = TraceLevel.ToLower(CultureInfo.CurrentCulture);
  1980. if ((TraceLevel != "high") && (TraceLevel != "medium") &&
  1981. (TraceLevel != "low") && (TraceLevel != "duration") &&
  1982. (TraceLevel != "duration-result"))
  1983. {
  1984. Console.WriteLine(Msg(Properties.Resources.locTraceInvalidLevelErr), TraceLevel);
  1985. return false;
  1986. }
  1987. return true;
  1988. }
  1989. }
  1990. // --------------------
  1991. // -Td validate
  1992. private static bool ValidateOption_Td
  1993. {
  1994. get
  1995. {
  1996. if (TraceDelim.Length > 1)
  1997. {
  1998. Console.WriteLine(Msg(Properties.Resources.locTraceInvalidDelimErr), TraceDelim);
  1999. return false;
  2000. }
  2001. return true;
  2002. }
  2003. }
  2004. // --------------------
  2005. // validate a timeout value
  2006. private static bool ValidateTimeout(string Option, string Timeout)
  2007. {
  2008. if (Timeout.Length > 0)
  2009. {
  2010. int t;
  2011. if ((t = GetTimeout(Option, Timeout)) == -1) return false;
  2012. if (!CheckTimeout(Option, t, maxTimeout)) return false;
  2013. }
  2014. return true;
  2015. }
  2016. // ------------------------------------------------------------
  2017. // Get the available scripting variables from the OS
  2018. private static void GetScriptingVariables()
  2019. {
  2020. GetEnvVar("ASCMDUSER", ref UserName);
  2021. GetEnvVar("ASCMDPASSWORD", ref Password);
  2022. GetEnvVar("ASCMDSERVER", ref Server);
  2023. GetEnvVar("ASCMDDBNAME", ref Database);
  2024. GetEnvVar("ASCMDQUERYTIMEOUT", ref Timeout);
  2025. GetEnvVar("ASCMDCONNECTTIMEOUT", ref ConnectTimeout);
  2026. GetEnvVar("ASCMDTRACEFORMAT", ref TraceFormat);
  2027. GetEnvVar("ASCMDTRACEDELIM", ref TraceDelim);
  2028. GetEnvVar("ASCMDTRACETIMEOUT", ref TraceTimeout);
  2029. GetEnvVar("ASCMDTRACELEVEL", ref TraceLevel);
  2030. GetEnvVar("ASCMDRUNINFO", ref RunInfo);
  2031. GetEnvVar("ASCMDRANDOMSEED", ref RandomSeed);
  2032. GetEnvVar("ASCMDTHINKTIMEMIN", ref ThinkTimeMin);
  2033. GetEnvVar("ASCMDTHINKTIMEMAX", ref ThinkTimeMax);
  2034. GetEnvVar("ASCMDCONNECTWAITMIN", ref ConnectWaitMin);
  2035. GetEnvVar("ASCMDCONNECTWAITMAX", ref ConnectWaitMax);
  2036. GetEnvVar("ASCMDEXTENDEDCONNECTSTRING", ref ExtendedConnectstring);
  2037. GetEnvVar("ASCMDEXITFILE", ref ExitFile);
  2038. }
  2039. // Get environment variable values
  2040. // OVERLOADED FOR STRING
  2041. private static void GetEnvVar(string var_name, ref string var)
  2042. {
  2043. if (Environment.GetEnvironmentVariable(var_name) != null)
  2044. {
  2045. var = Environment.GetEnvironmentVariable(var_name);
  2046. }
  2047. //else
  2048. //{
  2049. // var = ""; // empty string if not found as an environment variable
  2050. //}
  2051. }
  2052. // Get environment variable values
  2053. // OVERLOADED FOR INTEGER
  2054. private static void GetEnvVar(string var_name, ref int var)
  2055. {
  2056. if (Environment.GetEnvironmentVariable(var_name) != null)
  2057. {
  2058. string value = Environment.GetEnvironmentVariable(var_name);
  2059. if (!Int32.TryParse(value, out var)) var = 0;
  2060. }
  2061. //else
  2062. //{
  2063. // var = 0; // zero if not found as an environment variable
  2064. //}
  2065. }
  2066. private static void SetScriptingVariables()
  2067. {
  2068. // The -v entries are added to the substituteWith table as they are parsed
  2069. // Here we add command-line entries to the substitution table
  2070. // * = read-only, cannot be set by outside environments
  2071. // ** = set by ValidateSettings
  2072. substituteWith.Add("ASCMDUSER", UserName);
  2073. substituteWith.Add("ASCMDDOMAIN", Domain); // **
  2074. substituteWith.Add("ASCMDPASSWORD", Password);
  2075. substituteWith.Add("ASCMDSERVER", Server);
  2076. substituteWith.Add("ASCMDINSTANCE", Instance); // **
  2077. substituteWith.Add("ASCMDHTTPCONNECTION", httpConnection.ToString()); // **
  2078. substituteWith.Add("ASCMDDBNAME", Database);
  2079. substituteWith.Add("ASCMDINPUTFILE", InputFile); // *
  2080. substituteWith.Add("ASCMDOUTPUTFILE", OutputFile); // *
  2081. substituteWith.Add("ASCMDOUTPUTRESULTSTATFILE", OutputResultStatFile); // *
  2082. substituteWith.Add("ASCMDRUNINFO", RunInfo);
  2083. substituteWith.Add("ASCMDQUERYTIMEOUT", Timeout);
  2084. substituteWith.Add("ASCMDCONNECTTIMEOUT", ConnectTimeout);
  2085. substituteWith.Add("ASCMDTRACEFILE", TraceFile); // *
  2086. substituteWith.Add("ASCMDTRACEFORMAT", TraceFormat);
  2087. substituteWith.Add("ASCMDTRACEDELIM", TraceDelim);
  2088. substituteWith.Add("ASCMDTRACETIMEOUT", TraceTimeout);
  2089. substituteWith.Add("ASCMDTRACELEVEL", TraceLevel);
  2090. substituteWith.Add("ASCMDRANDOMSEED", RandomSeed.ToString(CultureInfo.CurrentCulture));
  2091. substituteWith.Add("ASCMDTHINKTIMEMIN", ThinkTimeMin.ToString(CultureInfo.CurrentCulture));
  2092. substituteWith.Add("ASCMDTHINKTIMEMAX", ThinkTimeMax.ToString(CultureInfo.CurrentCulture));
  2093. substituteWith.Add("ASCMDCONNECTWAITMIN", ConnectWaitMin.ToString(CultureInfo.CurrentCulture));
  2094. substituteWith.Add("ASCMDCONNECTWAITMAX", ConnectWaitMax.ToString(CultureInfo.CurrentCulture));
  2095. substituteWith.Add("ASCMDEXTENDEDCONNECTSTRING", ExtendedConnectstring);
  2096. substituteWith.Add("ASCMDEXITFILE", ExitFile);
  2097. }
  2098. private static bool CheckValidChars(string TypeOfCheck, int MinLen, int MaxLen, string InvalidChars, string TestString)
  2099. {
  2100. if (MinLen >= 0) // negative MinLen means don't check it
  2101. if (TestString.Length < MinLen)
  2102. {
  2103. Console.WriteLine(Msg(Properties.Resources.locMinErr), TypeOfCheck, TestString, TestString.Length.ToString(CultureInfo.CurrentCulture), MinLen.ToString(CultureInfo.CurrentCulture));
  2104. return false;
  2105. }
  2106. if (MaxLen >= 0) // negative MaxLen means don't check it
  2107. if (TestString.Length > MaxLen)
  2108. {
  2109. Console.WriteLine(Msg(Properties.Resources.locMaxErr), TypeOfCheck, TestString, TestString.Length.ToString(CultureInfo.CurrentCulture), MaxLen.ToString(CultureInfo.CurrentCulture));
  2110. return false;
  2111. }
  2112. if (TestString.Length > 0) // if we don't have any invalid characters provided, then don't check
  2113. {
  2114. int pos = TestString.IndexOfAny(InvalidChars.ToCharArray()) + 1;
  2115. if (pos > 0)
  2116. {
  2117. Console.WriteLine(Msg(Properties.Resources.locInvalidCharErr), TypeOfCheck, TestString, pos.ToString(CultureInfo.CurrentCulture));
  2118. return false;
  2119. }
  2120. }
  2121. return true;
  2122. }
  2123. private static int GetTimeout(string TypeOfCheck, string Timeout)
  2124. {
  2125. int ReturnTimeout;
  2126. if (Int32.TryParse(Timeout.ToString((CultureInfo.CurrentCulture)),
  2127. out ReturnTimeout))
  2128. {
  2129. return ReturnTimeout;
  2130. }
  2131. else
  2132. {
  2133. Console.WriteLine(Msg(Properties.Resources.locIntegerErr), TypeOfCheck, Timeout);
  2134. return -1;
  2135. }
  2136. }
  2137. private static bool CheckTimeout(string TypeOfCheck, int Timeout, int MaxTimeout)
  2138. {
  2139. if (MaxTimeout > 0) // -1 maxTimeout won't run check
  2140. {
  2141. if ((Timeout <= 0) || (Timeout > MaxTimeout))
  2142. {
  2143. Console.WriteLine(Msg(Properties.Resources.locRangeErr), TypeOfCheck, Timeout, MaxTimeout);
  2144. return false;
  2145. }
  2146. }
  2147. return true;
  2148. }
  2149. private static bool OptionSeen(string opt)
  2150. {
  2151. Console.WriteLine(Msg(Properties.Resources.locRepeatedOption), opt);
  2152. return false;
  2153. }
  2154. private static bool MissingArg(string opt)
  2155. {
  2156. Console.WriteLine(Msg(Properties.Resources.locParseMissingArgErr), opt);
  2157. return false;
  2158. }
  2159. // --------------------------------------------------------------------
  2160. // Supporting routine -- to show the help message
  2161. private static void ShowHelp()
  2162. {
  2163. string msg = "\n" +
  2164. "usage: ascmd.exe" +
  2165. "\n [-S server[:port][\\instance]] " +
  2166. "\n || http[s]://server:port/<vdir>/msmdpump.dll " +
  2167. "\n [-U domain\\username] [-P password] " +
  2168. "\n [-d database] [-xc extended-connect-string] " +
  2169. "\n [-i input-file] [-o output-file] " +
  2170. "\n [-t query-timeout-sec] [-tc connect-timeout-sec] " +
  2171. "\n [-T trace-file] [-Tt trace-timeout-sec] " +
  2172. "\n [-Tf text|csv] [-Td delim-char] " +
  2173. "\n [-ThinkTimeMin sec] [-ThinkTimeMax sec] " +
  2174. "\n [-ConnectWaitMin sec] [-ConnectWaitMax sec] " +
  2175. "\n [-Tl high|medium|low|duration|duration-result] " +
  2176. "\n [-oResultStat statistics-output-file] " +
  2177. "\n [-NoResultStatHeader] " +
  2178. "\n [-RunInfo run-info-string] " +
  2179. "\n [-RandomSeed seed-int] " +
  2180. "\n [-Xf exit-file] " +
  2181. "\n [-v var=value...] " +
  2182. "\n [-Q \"cmdline XMLA script, MDX query or DMX statement\" " +
  2183. "\n [-? show syntax summary] " +
  2184. "\n\n Note: either -i or -Q must be specified, but not both.";
  2185. // OK write it out
  2186. Console.WriteLine(msg);
  2187. }
  2188. }
  2189. }
  2190. namespace Microsoft.Samples.SqlServer.ASCmd.RandomHelper
  2191. {
  2192. /// <summary>
  2193. /// ran2 random number generator.
  2194. /// </summary>
  2195. ///
  2196. /// This is the Numerical Recipes ran2 random number generator.
  2197. /// Long period (> 2 x 10^18) random number generator of L'Ecuyer
  2198. /// with Bays-Durham shuffle and added safeguards.
  2199. /// Returns a uniform random deviate between 0.0 and 1.0 (exclusive
  2200. /// of the endpoint values).
  2201. ///
  2202. /// The motivation of this class is to improve upon System.Random.
  2203. /// We were using consecutive seed values, and found bad correlations
  2204. /// between parallel streams.
  2205. /// See http://www-cs-faculty.stanford.edu/~knuth/news02.html#rng, where
  2206. /// Don Knuth describes the problem and his recommendation for initializing his subtractive RNG.
  2207. ///
  2208. /// Another approach is to transform the seed, and also use that to consume a count
  2209. /// of initial values based on that seed.
  2210. ///
  2211. /// Another approach is to initialize using Knuth's code.
  2212. /// This is a little tricky in C# because we need to overwrite the private
  2213. /// members, and requires serializing to a byte stream, overwriting the bytes,
  2214. /// then deserializing from the bytes stream.
  2215. ///
  2216. [Serializable]
  2217. public class HighPerformanceRandom //: System.Random
  2218. {
  2219. /*
  2220. public override int Next()
  2221. {
  2222. throw new NotImplementedException();
  2223. }
  2224. public override double NextDouble()
  2225. {
  2226. return NextInternal();
  2227. }
  2228. */
  2229. /// <summary>
  2230. // Returns a 32-bit signed integer greater than or equal to zero, and less than maxValue;
  2231. // that is, the range of return values includes zero but not maxValue.
  2232. /// </summary>
  2233. public int Next(int maxValue)
  2234. {
  2235. return Next(0, maxValue);
  2236. }
  2237. /// <summary>
  2238. // Returns a 32-bit signed integer greater than or equal to minValue and less than maxValue;
  2239. // that is, the range of return values includes minValue but not maxValue.
  2240. // If minValue equals maxValue, minValue is returned.
  2241. /// <summary>
  2242. public int Next(int minValue, int maxValue)
  2243. {
  2244. double d = NextInternal();
  2245. if (minValue >= maxValue)
  2246. return minValue;
  2247. int j = minValue + (int)((maxValue - minValue) * d);
  2248. return j;
  2249. }
  2250. public HighPerformanceRandom()
  2251. {
  2252. throw new NotImplementedException();
  2253. }
  2254. public HighPerformanceRandom(int seed)
  2255. {
  2256. SetSeed(seed);
  2257. }
  2258. public void SetSeed(int seed)
  2259. {
  2260. // Pick a seed if user chooses default (<=0).
  2261. // Note that clock time may introduce correlation if many instances are started at the same time,
  2262. // since clock tick granularity is about 16 millisec, so different client runs
  2263. // should just use client number (e.g. 1, 2, 3, etc.) which also gives a
  2264. // reproducibile sequence.
  2265. if (seed <= 0)
  2266. seed = Guid.NewGuid().GetHashCode();
  2267. //Seed = (int)(DateTime.Now.Ticks);
  2268. else
  2269. seed = -seed;
  2270. // Note that NR does initialization if Dum<0, and we keep code flavor.
  2271. // Be sure to prevent Seed==0.
  2272. if (seed <= 0)
  2273. {
  2274. if (-seed < 1)
  2275. seed = 1;
  2276. else
  2277. seed = -seed;
  2278. }
  2279. _Dum = seed;
  2280. _Dum2 = _Dum;
  2281. // Load the shuffle table, after 8 warmups.
  2282. _iv = new int[NTAB];
  2283. for (int j = NTAB + 7; j >= 0; j--)
  2284. {
  2285. int k = _Dum / IQ1;
  2286. _Dum = IA1 * (_Dum - k * IQ1) - k * IR1;
  2287. if (_Dum < 0)
  2288. _Dum += IM1;
  2289. if (j < NTAB)
  2290. _iv[j] = _Dum;
  2291. }
  2292. _iy = _iv[0];
  2293. }
  2294. private double NextInternal()
  2295. {
  2296. // Compute Dum=(Dum*IA1)%IM1 without overflows by Schrage's method.
  2297. int k = _Dum / IQ1;
  2298. _Dum = IA1 * (_Dum - k * IQ1) - k * IR1;
  2299. if (_Dum < 0)
  2300. _Dum += IM1;
  2301. // Compute Dum2=(Dum2*IA2)%IM2.
  2302. k = _Dum2 / IQ2;
  2303. _Dum2 = IA2 * (_Dum2 - k * IQ2) - k * IR2;
  2304. if (_Dum2 < 0)
  2305. _Dum2 += IM2;
  2306. // Here Dum is shuffled.
  2307. // Dum and Dum2 are combined to generate output.
  2308. int j = _iy / NDIV; // Will be in range 0..NTAB-1.
  2309. _iy = _iv[j] - _Dum2;
  2310. _iv[j] = _Dum;
  2311. if (_iy < 1)
  2312. _iy += IMM1;
  2313. double temp = AM * _iy;
  2314. if (temp > RNMX)
  2315. return RNMX; // Avoid endpoint value.
  2316. else
  2317. return temp;
  2318. }
  2319. private class TestPoint
  2320. {
  2321. public int Dum; public int Dum2; public int iy; public double dx;
  2322. public TestPoint(int dum, int dum2, int y, double x) { Dum = dum; Dum2 = dum2; iy = y; dx = x; }
  2323. };
  2324. // Compare the first 50 or so numbers.
  2325. public static void TestFirst50()
  2326. {
  2327. // Values taken from the C version.
  2328. TestPoint[] rg = new TestPoint[] {
  2329. // Dum, Dum2, iy, x
  2330. new TestPoint( 1454538876, 40692, 612850790, 0.285380899906),
  2331. new TestPoint( 819059838, 1655838864, 544082547, 0.253358185291),
  2332. new TestPoint( 1113702789, 2103410263, 200722134, 0.093468531966),
  2333. new TestPoint( 1271983233, 1872071452, 1306737071, 0.608496904373),
  2334. new TestPoint( 1776642162, 652912057, 1940080159, 0.903420269489),
  2335. new TestPoint( 263600716, 1780294415, 420634462, 0.195873185992),
  2336. new TestPoint( 1427272131, 535353314, 994185124, 0.462953537703),
  2337. new TestPoint( 689175412, 525453832, 2016532872, 0.939021348953),
  2338. new TestPoint( 828503285, 1422611300, 273193743, 0.127215757966),
  2339. new TestPoint( 1026683959, 1336516156, 893205208, 0.415931105614),
  2340. new TestPoint( 371375236, 498340277, 1146195661, 0.533738970757),
  2341. new TestPoint( 1769920907, 1924298326, 230738686, 0.107446074486),
  2342. new TestPoint( 1902232084, 2007787254, 858287020, 0.399671047926),
  2343. new TestPoint( 507202204, 2020508212, 1396661036, 0.650371015072),
  2344. new TestPoint( 1469320506, 2118231989, 58136255, 0.027071803808),
  2345. new TestPoint( 1733222833, 1554910725, 165302143, 0.076974809170),
  2346. new TestPoint( 196772577, 1123836963, 148146270, 0.068985983729),
  2347. new TestPoint( 983154120, 514716691, 1829539448, 0.851945757866),
  2348. new TestPoint( 177567083, 445999725, 1368689500, 0.637345731258),
  2349. new TestPoint( 1293632758, 238604751, 1230715755, 0.573096692562),
  2350. new TestPoint( 477376060, 532080813, 1937627442, 0.902278125286),
  2351. new TestPoint( 2006855518, 504813878, 1906270400, 0.887676358223),
  2352. new TestPoint( 1463825993, 1207612141, 799243377, 0.372176706791),
  2353. new TestPoint( 919103077, 1438105654, 746284058, 0.347515612841),
  2354. new TestPoint( 1334506703, 472649818, 446453259, 0.207896009088),
  2355. new TestPoint( 1772419847, 205072612, 1222199519, 0.569131016731),
  2356. new TestPoint( 963089783, 1841722389, 783137233, 0.364676713943),
  2357. new TestPoint( 482038927, 491794886, 842711817, 0.392418295145),
  2358. new TestPoint( 1755745675, 1867189230, 787496536, 0.366706669331),
  2359. new TestPoint( 1630159468, 1701490540, 928031949, 0.432148575783),
  2360. new TestPoint( 1535209990, 40786521, 330588715, 0.153942376375),
  2361. new TestPoint( 1125220245, 1827928504, 1346239017, 0.626891434193),
  2362. new TestPoint( 422501572, 1831677004, 1609439316, 0.749453604221),
  2363. new TestPoint( 987294072, 1894317675, 732380379, 0.341041207314),
  2364. new TestPoint( 477372060, 1805707394, 1783254487, 0.830392599106),
  2365. new TestPoint( 1846799518, 1700779863, 559637130, 0.260601371527),
  2366. new TestPoint( 779026859, 1186685623, 2074500728, 0.966014742851),
  2367. new TestPoint( 1256819081, 299661202, 528842083, 0.246261298656),
  2368. new TestPoint( 588628800, 402892262, 1386554594, 0.645664811134),
  2369. new TestPoint( 1940567779, 603657338, 1966327796, 0.915642797947),
  2370. new TestPoint( 1168437952, 1109280134, 795570357, 0.370466321707),
  2371. new TestPoint( 1011561255, 873649147, 756510321, 0.352277576923),
  2372. new TestPoint( 841862146, 1090902678, 2068142139, 0.963053762913),
  2373. new TestPoint( 844740826, 382432447, 874386634, 0.407168030739),
  2374. new TestPoint( 68129944, 1276424170, 258785820, 0.120506539941),
  2375. new TestPoint( 994937769, 1218837426, 683394658, 0.318230450153),
  2376. new TestPoint( 1389597872, 803438887, 1821416735, 0.848163306713),
  2377. new TestPoint( 724837012, 247923428, 2077127217, 0.967237770557),
  2378. new TestPoint( 1862679853, 1770607073, 1221617315, 0.568859934807),
  2379. new TestPoint( 559616901, 1474978066, 1635595279, 0.761633455753),
  2380. new TestPoint( 699565213, 1941426420, 183528431, 0.085462085903),
  2381. new TestPoint( 2101672840, 1052083627, 2078554055, 0.967902183533),
  2382. new TestPoint( 880692680, 1305390819, 557289034, 0.259507954121),
  2383. new TestPoint( 1979112253, 961332483, 1965177938, 0.915107309818),
  2384. new TestPoint( 1593822354, 2131285451, 1184636063, 0.551639199257),
  2385. new TestPoint( 1388302545, 150503477, 1619417430, 0.754100024700),
  2386. new TestPoint( 433227946, 1812315535, 1034733240, 0.481835246086),
  2387. new TestPoint( 695710708, 16345161, 293867502, 0.136842727661),
  2388. new TestPoint( 338842743, 1544921121, 1727782686, 0.804561555386),
  2389. new TestPoint( 1389785183, 501233406, 953305470, 0.443917483091), };
  2390. HighPerformanceRandom r = new HighPerformanceRandom(1);
  2391. Console.WriteLine("L'Ecuyer LCG with Bays-Durham shuffle.");
  2392. Console.WriteLine("i, Dum, Dum2, iy, x");
  2393. double dx = 0.0;
  2394. bool fGotError = false;
  2395. for (int i = 0; i < rg.Length; i++)
  2396. {
  2397. dx = r.NextInternal();
  2398. Console.WriteLine("Expect [{0:######0}]: {1}, {2}, {3}, {4:f12}", i, rg[i].Dum, rg[i].Dum2, rg[i].iy, rg[i].dx);
  2399. Console.WriteLine("Got [{0:######0}]: {1}, {2}, {3}, {4:f12}", i, r._Dum, r._Dum2, r._iy, dx);
  2400. if (rg[i].Dum != r._Dum
  2401. || rg[i].Dum2 != r._Dum2
  2402. || rg[i].iy != r._iy
  2403. || System.Math.Abs(rg[i].dx - dx) > EPS)
  2404. {
  2405. fGotError = true;
  2406. Console.WriteLine("***** Diff *****");
  2407. }
  2408. }
  2409. Console.WriteLine("{0}", !fGotError ? "OK" : "Error");
  2410. }
  2411. // Show a matrix of values produced with different seeds.
  2412. public static void TestShowWithSeeds()
  2413. {
  2414. const int NUMSEED = 20;
  2415. const int NUMVAL = 40;
  2416. // For this scenario, the system RNG performs poorly (has patterns).
  2417. Console.WriteLine("System.Random");
  2418. for (int Seed = 1; Seed <= NUMSEED; Seed++)
  2419. {
  2420. System.Random r = new System.Random(Seed);
  2421. Console.Write("{0:00}", Seed);
  2422. for (int j = 0; j < NUMVAL; j++)
  2423. Console.Write(" {0:00}", r.Next(5, 25));
  2424. Console.WriteLine("");
  2425. }
  2426. Console.WriteLine("ASPPerfRand_ran2: LCG from L'Ecuyer with Bays-Durham shuffle)");
  2427. for (int Seed = 1; Seed <= NUMSEED; Seed++)
  2428. {
  2429. HighPerformanceRandom r = new HighPerformanceRandom(Seed);
  2430. Console.Write("{0:00}", Seed);
  2431. for (int j = 0; j < NUMVAL; j++)
  2432. Console.Write(" {0:00}", r.Next(5, 25));
  2433. Console.WriteLine("");
  2434. }
  2435. }
  2436. // Constants.
  2437. private const int IM1 = 2147483563;
  2438. private const int IM2 = 2147483399;
  2439. private const double AM = (1.0 / IM1);
  2440. private const int IMM1 = (IM1 - 1);
  2441. private const int IA1 = 40014;
  2442. private const int IA2 = 40692;
  2443. private const int IQ1 = 53668;
  2444. private const int IQ2 = 52774;
  2445. private const int IR1 = 12211;
  2446. private const int IR2 = 3791;
  2447. private const int NTAB = 32;
  2448. private const int NDIV = (1 + IMM1 / NTAB);
  2449. private const double EPS = 1.2e-7;
  2450. private const double RNMX = (1.0 - EPS);
  2451. // Vars for state of LCG and shuffle table.
  2452. private Int32 _Dum;
  2453. private Int32 _Dum2;
  2454. private Int32 _iy;
  2455. private Int32[] _iv;
  2456. }
  2457. }