PageRenderTime 72ms CodeModel.GetById 23ms 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

Large files files are truncated, but you can click here to view the full 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. StringBu

Large files files are truncated, but you can click here to view the full file