Rob van der Woude's Scripting Pages
Powered by GeSHi

Source code for batcodecheck.cs

(view source code of batcodecheck.cs as plain text)

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Diagnostics;
  4. using System.IO;
  5. using System.Linq;
  6. using System.Runtime.InteropServices;
  7. using System.Text;
  8. using System.Text.RegularExpressions;
  9.  
  10.  
  11. namespace RobvanderWoude
  12. {
  13. 	public partial class BatCodeCheck
  14. 	{
  15. 		public static string progver = "0.42";
  16.  
  17.  
  18. 		#region Global Variables and Constants
  19.  
  20.  
  21. 		public const int maxvarlen = 32; // Some day, we may have to adjust the accepted variable name length if 32 characters is no longer enough ([\w\.-]{1,32})
  22. 		public const RegexOptions ignorecase = RegexOptions.IgnoreCase;
  23. 		public const string dynvarpattern = @"__APPDIR__|__CD__|CD|CMDCMDLINE|CMDEXTVESION|DATE|ERRORLEVEL|HIGHESTNUMANODENUMBER|RANDOM|TIME";
  24. 		public const string intcmdpattern = @"ASSOC|BREAK|CALL|CD|CHDIR|CLS|COLOR|COPY|DATE|DEL|DIR|DPATH|ECHO|ENDLOCAL|ERASE|EXIT|FOR|FTYPE|GOTO|IF|KEYS|MD|MKDIR|MKLINK|MOVE|PATH|PAUSE|POPD|PROMPT|PUSHD|RD|REM|REN|RENAME|RMDIR|SET|SETLOCAL|SHIFT|START|TIME|TITLE|TYPE|VER|VERIFY|VOL";
  25. 		public const string notx32pattern = @"BREAK|CTTY|FASTOPEN|FASTOPEN\.COM|FASTOPEN\.EXE|KEYS|SHARE|SHARE\.EXE";
  26. 		public const string notx64pattern = @"APPEND|APPEND\.COM|APPEND\.EXE|DEBUG|DEBUG\.COM|DEBUG\.EXE";
  27. 		public const string sysvarpattern = @"AllUsersProfile|AppData|CommonProgramFiles|CommonProgramFiles\(x86\)|CommonProgramW6432|ComputerName|ComSpec|HomeDrive|HomePath|LocalAppData|LogonServer|NUMBER_OF_PROCESSORS|OS|PATH|PATHEXT|PROCESSOR_ARCHITECTURE|PROCESSOR_IDENTIFIER|PROCESSOR_LEVEL|PROCESSOR_REVISION|ProgramData|ProgramFiles|ProgramFiles\(x86\)|ProgramW6432|PROMPT|SystemDrive|SystemRoot|TEMP|TMP|UserDomain|UserName|UserProfile|windir";
  28.  
  29.  
  30. 		public static int commentlines = 0;
  31. 		public static int lasterrline = -1;
  32. 		public static int lastline = -1;
  33. 		public static int linenum = 0;
  34. 		public static int lineswitherrors = 0;
  35. 		public static int mysteryvars = 0;
  36. 		public static int obsoletecmd = 0;
  37. 		public static int parenthesis = 0;
  38. 		public static int rc = 1;
  39. 		public static int setlocal = 0;
  40. 		public static int unterminatedexclamations = 0;
  41. 		public static int unterminateddoublequotes = 0;
  42. 		public static int unterminatedpercentsigns = 0;
  43. 		public static int unterminatedsinglequotes = 0;
  44. 		public static bool addtimestamp = false;
  45. 		public static bool checkall = true;
  46. 		public static bool checkbadpr = false;
  47. 		public static bool checkbestpr = false;
  48. 		public static bool checkcmdargs = false;
  49. 		public static bool checkcommerr = false;
  50. 		public static bool checkenvvars = false;
  51. 		public static bool checklabels = false;
  52. 		public static bool checksubs = false;
  53. 		public static bool checkundef = false;
  54. 		public static bool checkupdates = false;
  55. 		public static bool checkvulnr = false;
  56. 		public static bool debugmode = false;
  57. 		public static bool delayedvarexp = false;
  58. 		public static bool delayedvarglobal = false;
  59. 		public static bool dispnums = true;
  60. 		public static bool errinline = false;
  61. 		public static bool htmlopen = false;
  62. 		public static bool includelocalvars = false;
  63. 		public static bool incomment = false;
  64. 		public static bool log2html = false;
  65. 		public static bool logging = false;
  66. 		public static bool openalllogs = false;
  67. 		public static bool openlog = false;
  68. 		public static bool openhtml = false;
  69. 		public static bool replacehtml = true;
  70. 		public static bool replacelog = true;
  71. 		public static bool spanopen = false;
  72. 		public static bool splitoneliners = false;
  73. 		public static bool verbosesummary = true;
  74. 		public static bool wrap = false;
  75. 		public static string allargs;
  76. 		public static string allsrc;
  77. 		public static string allsrc_nocomments;
  78. 		public static string allerrors = string.Empty;
  79. 		public static string allbadpr = string.Empty;
  80. 		public static string alltips = string.Empty;
  81. 		public static string allwarnings = string.Empty;
  82. 		public static string allerrsum = string.Empty;
  83. 		public static string checks = "ABELMSUV";
  84. 		public static string encoding;
  85. 		public static string html = string.Empty;
  86. 		public static string htmlfile = string.Empty;
  87. 		public static string logfile = string.Empty;
  88. 		public static string newline = Environment.NewLine;
  89. 		public static string source = string.Empty;
  90. 		public static string timestamp = string.Empty;
  91. 		public static string[] path = ( Environment.CurrentDirectory + ";" + Environment.GetEnvironmentVariable( "PATH" ) ).Split( ';' );
  92. 		public static string[] pathext = ( ";" + Environment.GetEnvironmentVariable( "PATHEXT" ).ToLower( ) ).Split( ';' );
  93. 		public static Dictionary<string, string> tests;
  94. 		public static SortedList<string, int> labels = new SortedList<string, int>( );
  95. 		public static SortedList<string, int> envvars = new SortedList<string, int>( );
  96. 		public static SortedList<string, int> subroutines = new SortedList<string, int>( );
  97. 		public static List<string> upcaselabels = new List<string>( );
  98. 		public static List<string> gotoerrors = new List<string>( );
  99. 		public static List<string> callerrors = new List<string>( );
  100. 		public static List<string> upcaseenvvars = new List<string>( );
  101. 		public static List<string> envvarerrors = new List<string>( );
  102. 		public static List<string> localenvvars = new List<string>( );
  103. 		public static List<int> incorrectlinefeeds = new List<int>( );
  104. 		public static StreamWriter htmlstream;
  105. 		public static ConsoleColor badprbgcolor = ConsoleColor.Yellow;
  106. 		public static ConsoleColor badprfgcolor = ConsoleColor.Red;
  107. 		public static ConsoleColor commentfgscolor = ConsoleColor.DarkYellow;
  108. 		public static ConsoleColor doublequotedfgcolor = ConsoleColor.Yellow;
  109. 		public static ConsoleColor echoedtextfgcolor = ConsoleColor.Cyan;
  110. 		public static ConsoleColor errorbgcolor = ConsoleColor.Red;
  111. 		public static ConsoleColor errorfgcolor = ConsoleColor.White;
  112. 		public static ConsoleColor extcmdbgcolor = ConsoleColor.Black;
  113. 		public static ConsoleColor extcmdfgcolor = ConsoleColor.White;
  114. 		public static ConsoleColor intcmdbgcolor = ConsoleColor.Black;
  115. 		public static ConsoleColor intcmdfgcolor = ConsoleColor.Yellow;
  116. 		public static ConsoleColor lastbgc = Console.BackgroundColor;
  117. 		public static ConsoleColor lastfgc = Console.ForegroundColor;
  118. 		public static ConsoleColor linenumbgcolor = ConsoleColor.Gray;
  119. 		public static ConsoleColor linenumfgcolor = ConsoleColor.DarkMagenta;
  120. 		public static ConsoleColor parenthesisfgcolor = ConsoleColor.Red;
  121. 		public static ConsoleColor redirectionfgcolor = ConsoleColor.Red;
  122. 		public static ConsoleColor reportbgcolor = Console.BackgroundColor;
  123. 		public static ConsoleColor reportfgcolor = ConsoleColor.Red;
  124. 		public static ConsoleColor singlequotedfgcolor = ConsoleColor.DarkGray;
  125. 		public static ConsoleColor tipsbgcolor = ConsoleColor.Blue;
  126. 		public static ConsoleColor tipsfgcolor = ConsoleColor.White;
  127. 		public static ConsoleColor variablesfgcolor = ConsoleColor.Green;
  128. 		public static ConsoleColor warningbgcolor = ConsoleColor.DarkYellow;
  129. 		public static ConsoleColor warningfgcolor = ConsoleColor.White;
  130. 		public static ConsoleColor windowbgcolor = Console.BackgroundColor;
  131. 		public static ConsoleColor windowfgcolor = Console.ForegroundColor;
  132. 		// Inline variables
  133. 		public static string badprmsg = string.Empty;
  134. 		public static string errormsg = string.Empty;
  135. 		public static string tipsmsg = string.Empty;
  136. 		public static string warnmsg = string.Empty;
  137. 		public static string message = string.Empty;
  138. 		public static string part = string.Empty;
  139. 		public static string buildword = string.Empty;
  140. 		public static string lastword = string.Empty;
  141.  
  142.  
  143. 		#endregion Global Variables and Constants
  144.  
  145.  
  146. 		[STAThread]
  147. 		static int Main( string[] args )
  148. 		{
  149. 			#region Initialize List of Tests
  150.  
  151. 			tests = new Dictionary<string, string>( );
  152. 			tests["A"] = "command line Arguments for batch commands";
  153. 			tests["B"] = "Best practice tips";
  154. 			tests["E"] = "Environment variables";
  155. 			tests["L"] = "Labels";
  156. 			tests["M"] = "common Mistakes";
  157. 			tests["S"] = "verbose Summary (variables, labels, subroutines)";
  158. 			tests["U"] = "Undefined environment variables";
  159. 			tests["V"] = "Vulnerabilities";
  160.  
  161. 			#endregion Initialize List of Tests
  162.  
  163.  
  164. 			#region Command Line Parsing
  165.  
  166. 			allargs = string.Join( " ", args );
  167.  
  168.  
  169. 			#region Command Line Arguments
  170.  
  171.  
  172. 			if ( args.Length == 0 || args.Length > 8 )
  173. 			{
  174. 				return ShowHelp( );
  175. 			}
  176. 			foreach ( string arg in args )
  177. 			{
  178. 				if ( arg[0] == '/' )
  179. 				{
  180. 					switch ( arg.ToUpper( ).Substring( 0, Math.Min( 2, arg.Length ) ) )
  181. 					{
  182. 						case "/?":
  183. 							return ShowHelp( );
  184. 						case "/C":
  185. 							if ( !checkall )
  186. 							{
  187. 								return ShowHelp( "Duplicate command line argument /C" );
  188. 							}
  189. 							if ( arg.Length < 4 || arg[2] != ':' )
  190. 							{
  191. 								return ShowHelp( "Invalid command line argument \"{0}\"", arg );
  192. 							}
  193. 							checks = arg.Substring( 3 ).ToUpper( );
  194. 							foreach ( char check in checks )
  195. 							{
  196. 								if ( !tests.Keys.Contains( check.ToString( ).ToUpper( ) ) )
  197. 								{
  198. 									return ShowHelp( "Invalid check \"{1}\" in command line argument \"{0}\"", arg, check.ToString( ) );
  199. 								}
  200. 							}
  201. 							checkall = false;
  202. 							checkcmdargs = ( checks.IndexOf( 'A' ) > -1 ); // command line Arguments for batch commands
  203. 							checkbestpr = ( checks.IndexOf( 'B' ) > -1 ); // Best practice tips
  204. 							checkenvvars = ( checks.IndexOf( 'E' ) > -1 ); // Environment variables
  205. 							checklabels = ( checks.IndexOf( 'L' ) > -1 ); // Labels
  206. 							checkcommerr = ( checks.IndexOf( 'M' ) > -1 ); //' common Mistakes
  207. 							verbosesummary = ( checks.IndexOf( 'S' ) > -1 ); // verbose Summary (variables, labels, subroutines)
  208. 							checkundef = ( checks.IndexOf( 'U' ) > -1 ); // Undefined environment variables
  209. 							checkvulnr = ( checks.IndexOf( 'V' ) > -1 ); // Vulnerabilities
  210. 							break;
  211. 						case "/D":
  212. 							if ( debugmode )
  213. 							{
  214. 								return ShowHelp( "Duplicate command line argument /D" );
  215. 							}
  216. 							debugmode = true;
  217. 							break;
  218. 						case "/E":
  219. 							if ( includelocalvars )
  220. 							{
  221. 								return ShowHelp( "Duplicate command line argument /E" );
  222. 							}
  223. 							includelocalvars = true;
  224. 							break;
  225. 						case "/H":
  226. 							if ( log2html )
  227. 							{
  228. 								return ShowHelp( "Duplicate command line argument /H" );
  229. 							}
  230. 							log2html = true;
  231. 							if ( arg.Length > 3 && arg.ToUpper( ).Substring( 2, 1 ) == ":" )
  232. 							{
  233. 								htmlfile = arg.Substring( 3 );
  234. 							}
  235. 							break;
  236. 						case "/L":
  237. 							if ( logging )
  238. 							{
  239. 								return ShowHelp( "Duplicate command line argument /L" );
  240. 							}
  241. 							logging = true;
  242. 							if ( arg.Length > 3 && arg.ToUpper( ).Substring( 2, 1 ) == ":" )
  243. 							{
  244. 								logfile = arg.Substring( 3 );
  245. 							}
  246. 							break;
  247. 						case "/O":
  248. 							if ( openalllogs )
  249. 							{
  250. 								return ShowHelp( "Duplicate command line argument /O" );
  251. 							}
  252. 							openalllogs = true;
  253. 							break;
  254. 						case "/S":
  255. 							if ( splitoneliners )
  256. 							{
  257. 								return ShowHelp( "Duplicate command line argument /S" );
  258. 							}
  259. 							splitoneliners = true;
  260. 							break;
  261. 						case "/T":
  262. 							if ( addtimestamp )
  263. 							{
  264. 								return ShowHelp( "Duplicate command line argument /T" );
  265. 							}
  266. 							addtimestamp = true;
  267. 							break;
  268. 						case "/U":
  269. 							if ( checkupdates )
  270. 							{
  271. 								return ShowHelp( "Duplicate command line argument /U" );
  272. 							}
  273. 							checkupdates = true;
  274. 							break;
  275. 						case "/W":
  276. 							if ( wrap )
  277. 							{
  278. 								return ShowHelp( "Duplicate command line argument /W" );
  279. 							}
  280. 							wrap = true;
  281. 							break;
  282. 						default:
  283. 							return ShowHelp( "Invalid command line argument \"{0}\"", arg );
  284. 					}
  285. 				}
  286. 				else
  287. 				{
  288. 					if ( string.IsNullOrEmpty( source ) )
  289. 					{
  290. 						source = arg;
  291. 					}
  292. 					else
  293. 					{
  294. 						return ShowHelp( "Invalid command line argument \"{0}\"", arg );
  295. 					}
  296. 				}
  297. 			}
  298.  
  299. 			if ( checkupdates )
  300. 			{
  301. 				string updatecheckurl = "http://www.robvanderwoude.com/getlatestver.php?progfile=BatCodeCheck.exe&version=" + progver;
  302. 				Process.Start( updatecheckurl );
  303. 			}
  304.  
  305.  
  306. 			#endregion Command Line Arguments
  307.  
  308.  
  309. 			#region Validate paths
  310.  
  311.  
  312. 			if ( string.IsNullOrEmpty( source ) )
  313. 			{
  314. 				return ShowHelp( "Please specify a source file" );
  315. 			}
  316. 			if ( !addtimestamp && source.IndexOfAny( "/?*".ToCharArray( ) ) > -1 )
  317. 			{
  318. 				return ShowHelp( );
  319. 			}
  320. 			if ( !File.Exists( source ) )
  321. 			{
  322. 				return ShowHelp( "Source file not found" );
  323. 			}
  324. 			if ( !string.IsNullOrWhiteSpace( source ) )
  325. 			{
  326. 				source = Path.GetFullPath( source );
  327. 			}
  328. 			if ( !string.IsNullOrEmpty( logfile ) )
  329. 			{
  330. 				if ( !Directory.Exists( Directory.GetParent( logfile ).FullName ) )
  331. 				{
  332. 					return ShowHelp( "Invalid path to log file" );
  333. 				}
  334. 				logfile = Path.GetFullPath( logfile );
  335. 			}
  336. 			if ( !string.IsNullOrEmpty( htmlfile ) )
  337. 			{
  338. 				if ( !Directory.Exists( Directory.GetParent( htmlfile ).FullName ) )
  339. 				{
  340. 					return ShowHelp( "Invalid path to HTML file" );
  341. 				}
  342. 				htmlfile = Path.GetFullPath( htmlfile );
  343. 			}
  344.  
  345.  
  346. 			#endregion Validate paths
  347.  
  348.  
  349. 			#region BatCodeCheck Environment Variable
  350.  
  351.  
  352. 			// Local environment variable %BatCodeCheck% can be used to set defaults, but it is overruled by command line arguments
  353. 			try
  354. 			{
  355. 				string envargs = Environment.GetEnvironmentVariable( "BatCodeCheck" );
  356. 				if ( !string.IsNullOrWhiteSpace( envargs ) )
  357. 				{
  358. 					envargs = envargs.ToUpper( );
  359. 				}
  360. 				if ( new Regex( @"^\s*(/[EHILOSTW]\s*)+$" ).IsMatch( envargs ) )
  361. 				{
  362. 					foreach ( Match match in new Regex( "/[DEHILOSTW]" ).Matches( envargs ) )
  363. 					{
  364. 						switch ( match.ToString( ) )
  365. 						{
  366. 							case "/D":
  367. 								if ( !debugmode )
  368. 								{
  369. 									debugmode = true;
  370. 									allargs += " /D";
  371. 								}
  372. 								break;
  373. 							case "/E":
  374. 								if ( !includelocalvars )
  375. 								{
  376. 									includelocalvars = true;
  377. 									allargs += " /E";
  378. 								}
  379. 								break;
  380. 							case "/H":
  381. 								if ( !log2html )
  382. 								{
  383. 									log2html = true;
  384. 									allargs += " /H";
  385. 								}
  386. 								break;
  387. 							case "/L":
  388. 								if ( !logging )
  389. 								{
  390. 									logging = true;
  391. 									allargs += " /L";
  392. 								}
  393. 								break;
  394. 							case "/O":
  395. 								if ( !openalllogs )
  396. 								{
  397. 									openalllogs = true;
  398. 									allargs += " /O";
  399. 								}
  400. 								break;
  401. 							case "/S":
  402. 								if ( !splitoneliners )
  403. 								{
  404. 									splitoneliners = true;
  405. 									allargs += " /S";
  406. 								}
  407. 								break;
  408. 							case "/T":
  409. 								if ( !addtimestamp )
  410. 								{
  411. 									addtimestamp = true;
  412. 									allargs += " /T";
  413. 								}
  414. 								break;
  415. 							case "/W":
  416. 								if ( !wrap )
  417. 								{
  418. 									wrap = true;
  419. 									allargs += " /W";
  420. 								}
  421. 								break;
  422. 						}
  423. 					}
  424. 				}
  425. 			}
  426. 			catch ( Exception )
  427. 			{
  428. 				// Environment variable BatCodeCheck not found
  429. 			}
  430.  
  431.  
  432. 			#endregion BatCodeCheck Environment Variable
  433.  
  434.  
  435. 			#region Command Line Defaults
  436.  
  437.  
  438. 			if ( !includelocalvars )
  439. 			{
  440. 				pathext = ".com;.exe;.bat;.cmd".Split( ';' );
  441. 			}
  442. 			if ( addtimestamp )
  443. 			{
  444. 				AddTimeStamp( );
  445. 			}
  446. 			if ( log2html && string.IsNullOrWhiteSpace( htmlfile ) )
  447. 			{
  448. 				htmlfile = Path.Combine( Path.GetDirectoryName( source ), Path.GetFileNameWithoutExtension( source ) + timestamp + ".html" );
  449. 			}
  450. 			if ( logging && string.IsNullOrWhiteSpace( logfile ) )
  451. 			{
  452. 				logfile = Path.Combine( Path.GetDirectoryName( source ), Path.GetFileNameWithoutExtension( source ) + timestamp + ".log" );
  453. 			}
  454.  
  455.  
  456. 			#endregion Command Line Defaults
  457.  
  458.  
  459. 			#region Command Line Dependencies
  460.  
  461.  
  462. 			openalllogs = openalllogs && ( logging || log2html );
  463. 			openhtml = openalllogs && log2html;
  464. 			openlog = openalllogs && logging;
  465.  
  466.  
  467. 			#endregion Command Line Dependencies
  468.  
  469. 			#endregion Command Line Parsing
  470.  
  471.  
  472. #if DEBUG
  473. 			debugmode = true;
  474. #endif
  475.  
  476.  
  477. 			if ( File.Exists( source ) )
  478. 			{
  479. 				encoding = GetEncoding( source );
  480. 				ReadSource( source );
  481. 				CheckLinefeeds( );
  482. 				CollectEnvironmentVariables( );
  483. 				CollectLabels( );
  484. 				CreateHTML( );
  485.  
  486. 				// Now parse the source line by line
  487. 				rc = 0;
  488. 				foreach ( string line in allsrc.Split( new string[] { "\r\n", "\n", "\r" }, StringSplitOptions.None ) )
  489. 				{
  490. 					linenum += 1;
  491.  
  492. 					// If /S switch was used, split the line on parenthesis and ampersands, except parentheses surrounding the list in FOR loops, and except comments
  493. 					if ( splitoneliners && !new Regex( @"^\s*(::|REM)(\b|\s|$)", ignorecase ).IsMatch( line ) && line.IndexOfAny( "(&|)".ToCharArray( ) ) > -1 )
  494. 					{
  495. 						#region Split One-Liners
  496.  
  497. 						bool inelse = false;
  498. 						bool inforloop = false;
  499. 						string tmpline = string.Empty;
  500.  
  501. 						string pattern = @"(?<!\^)\((?!['\n\r])";
  502. 						Regex regex = new Regex( pattern );
  503. 						if ( regex.IsMatch( line ) )
  504. 						{
  505. 							MatchCollection matches = regex.Matches( line );
  506. 							tmpline = line.Substring( 0, matches[0].Index );
  507. 							if ( new Regex( @"\s%%[A-Z!]\s+IN\s+$", ignorecase ).IsMatch( tmpline ) )
  508. 							{
  509. 								inforloop = true;
  510. 							}
  511. 							else if ( new Regex( @"\)\s+ELSE\s+\(", ignorecase ).IsMatch( tmpline ) )
  512. 							{
  513. 								inelse = true;
  514. 							}
  515.  
  516. 						}
  517. 						if ( inforloop || inelse )
  518. 						{
  519. 							tmpline = regex.Replace( line, "(\n\t", 100, line.IndexOf( ')' ) + 1 );
  520. 						}
  521. 						else
  522. 						{
  523. 							tmpline = regex.Replace( line, "(\n\t" );
  524. 						}
  525.  
  526. 						pattern = @"(?<!['\^\n\r])\)";
  527. 						regex = new Regex( pattern );
  528. 						if ( inforloop || inelse )
  529. 						{
  530. 							tmpline = regex.Replace( tmpline, "\n)", 100, line.IndexOf( ')' ) + 1 );
  531. 						}
  532. 						else
  533. 						{
  534. 							tmpline = regex.Replace( tmpline, "\n)" );
  535. 						}
  536.  
  537. 						pattern = @"(?<!\^|2>|1>)&(?>!1|2)";
  538. 						regex = new Regex( pattern );
  539. 						tmpline = regex.Replace( tmpline, "\n\t" );
  540.  
  541. 						foreach ( string part in tmpline.Split( new string[] { "\n" }, StringSplitOptions.None ) )
  542. 						{
  543. 							if ( !string.IsNullOrWhiteSpace( part ) )
  544. 							{
  545. 								ParseLine( part );
  546. 							}
  547. 						}
  548.  
  549. 						#endregion Split One-Liners
  550. 					}
  551. 					else
  552. 					{
  553. 						ParseLine( line );
  554. 					}
  555. 				}
  556. 				DisplayMessagesSummary( );
  557. 				CloseHTML( );
  558. 				RestoreConsoleColors( );
  559. 				if ( OpenLogFile( ) + OpenHTMLFile( ) != 0 )
  560. 				{
  561. 					return 1;
  562. 				}
  563. 				return rc;
  564. 			}
  565. 			else
  566. 			{
  567. 				return ShowHelp( "File \"{0}\" not found", source );
  568. 			}
  569. 		}
  570.  
  571.  
  572. 		#region Subroutines
  573.  
  574.  
  575. 		public static void AddTimeStamp( )
  576. 		{
  577. 			if ( string.IsNullOrWhiteSpace( timestamp ) )
  578. 			{
  579. 				timestamp = DateTime.Now.ToString( ".yyyyMMddHHmmss" );
  580. 			}
  581. 		}
  582.  
  583.  
  584. 		public static void CheckBadPractices( string line )
  585. 		{
  586. 			if ( checkall || checkbadpr )
  587. 			{
  588. 				string pattern;
  589. 				Regex regex;
  590.  
  591. 				// SET dynamic variables
  592. 				#region SET dynamic variables
  593.  
  594. 				pattern = string.Format( @"\bSET\s+({0})=", dynvarpattern );
  595. 				regex = new Regex( pattern, ignorecase );
  596. 				if ( regex.IsMatch( line ) )
  597. 				{
  598. 					MatchCollection matches = regex.Matches( line );
  599. 					string dynvar = matches[0].Groups[1].Value;
  600. 					message = string.Format( "Setting dynamic variable \"{0}\" to a static value may cause problems later on in the batch file; consider using an alternative variable name instead", dynvar.ToUpper( ) );
  601. 					allbadpr += string.Format( "{0}Line {1,5:0}:\t{2}", newline, linenum, message );
  602. 					badprmsg += string.Format( "{0}{1}.", newline, message );
  603. 					errinline = true;
  604. 				}
  605.  
  606. 				#endregion SET dynamic variables
  607.  
  608. 				// SET system variables
  609. 				#region SET system variables
  610.  
  611. 				pattern = string.Format( @"\bSET\s+({0})=", sysvarpattern );
  612. 				regex = new Regex( pattern, ignorecase );
  613. 				if ( regex.IsMatch( line ) )
  614. 				{
  615. 					MatchCollection matches = regex.Matches( line );
  616. 					string dynvar = matches[0].Groups[1].Value;
  617. 					message = string.Format( "Setting system variable \"{0}\" may cause problems later on in the batch file; consider using an alternative variable name instead", dynvar.ToUpper( ) );
  618. 					allbadpr += string.Format( "{0}Line {1,5:0}:\t{2}", newline, linenum, message );
  619. 					badprmsg += string.Format( "{0}{1}.", newline, message );
  620. 					errinline = true;
  621. 				}
  622.  
  623. 				#endregion SET system variables
  624.  
  625. 				// Quoted paths
  626. 				// Disabled for now, as it is extremely hard, if possible at all, to extract a path from a command line without a true interpreter
  627. 				#region Quoted paths
  628. 				/*
  629. 			pattern = @"[^""=]((?:[A-Z]:|\\\\\w+\\\w+)(?:\\[^\\]+)*\\[^\s\\]+\s+[^\\]+(?:\\[^\\]+)*(?:\\|\b|$))";
  630. 			regex = new Regex( pattern, ignorecase );
  631. 			if ( regex.IsMatch( line ) )
  632. 			{
  633. 				MatchCollection matches = regex.Matches( line );
  634. 				string path = matches[0].Groups[1].ToString( );
  635. 				message = string.Format( "Whitespace found in unquoted path: \"{0}\"", path );
  636. 				allbadpr += string.Format( "{0}Line {1,5:0}:\t{2}", newline, linenum, message );
  637. 				badprmsg += string.Format( "{0}{1}.", newline, message );
  638. 				errinline = true;
  639. 			}
  640.  
  641. 			pattern = @"[^""]((?:[A-Z]:|\\\\\w+\\\w+)(?:\\[^\\]+)*\\[^\\]*(\(|\))[^\\]*(?:\\[^\\]+)*\\?(?:\b|$))";
  642. 			regex = new Regex( pattern, ignorecase );
  643. 			if ( regex.IsMatch( line ) )
  644. 			{
  645. 				MatchCollection matches = regex.Matches( line );
  646. 				string path = matches[0].Groups[1].ToString( );
  647. 				string spec = matches[0].Groups[2].ToString( );
  648. 				message = string.Format( "Parenthesis found in unquoted path: \"{1}\"; use doublequotes, especially inside code blocks", spec, path );
  649. 				allbadpr += string.Format( "{0}Line {1,5:0}:\t{2}", newline, linenum, message );
  650. 				badprmsg += string.Format( "{0}{1}.", newline, message );
  651. 				errinline = true;
  652. 			}
  653.  
  654. 			pattern = @"[^""=]((?:[A-Z]:|\\\\\w+\\\w+)(?:\\[^\\]+)*\\[^\\]*(&|'|,|\^)[^\\]*(?:\\[^\\]+)*\\?(?:\b|$))";
  655. 			regex = new Regex( pattern, ignorecase );
  656. 			if ( regex.IsMatch( line ) )
  657. 			{
  658. 				MatchCollection matches = regex.Matches( line );
  659. 				string path = matches[0].Groups[1].ToString( );
  660. 				string spec = matches[0].Groups[2].ToString( );
  661. 				message = string.Format( "Special character \"{0}\" found in unquoted path: \"{1}\"; use doublequotes", spec, path );
  662. 				allbadpr += string.Format( "{0}Line {1,5:0}:\t{2}", newline, linenum, message );
  663. 				badprmsg += string.Format( "{0}{1}.", newline, message );
  664. 				errinline = true;
  665. 			}
  666. 			*/
  667. 				#endregion Quoted paths
  668. 			}
  669. 		}
  670.  
  671.  
  672. 		public static void CheckBestPractices( string line )
  673. 		{
  674. 			if ( checkall || checkbestpr )
  675. 			{
  676. 				string pattern;
  677. 				Regex regex;
  678.  
  679. 				// Doublequotes in SET statements
  680. 				#region Doublequoted SET
  681.  
  682. 				pattern = @"(?:^|\b|\()(SET\s+""([\w_\.-]+)=([^""&\n\r]+)"")";
  683. 				regex = new Regex( pattern, ignorecase );
  684. 				if ( regex.IsMatch( line ) )
  685. 				{
  686. 					foreach ( Match match in regex.Matches( line ) )
  687. 					{
  688. 						string wrong = match.Groups[1].ToString( );
  689. 						string correct = string.Format( "SET {0}={1}", match.Groups[2].ToString( ), match.Groups[3].ToString( ) );
  690. 						message = string.Format( "Doublequoted SET statements are not recommended, except with /A (or /P) switch; use {0} instead of {1}", correct, wrong );
  691. 						alltips += string.Format( "{0}Line {1,5:0}:\t{2}", newline, linenum, message );
  692. 						tipsmsg += string.Format( "{0}{1}.", newline, message );
  693. 						errinline = true;
  694. 					}
  695. 				}
  696.  
  697. 				#endregion Doublequoted SET
  698.  
  699. 				// Devices with appended colons
  700. 				#region Devices with appended colons
  701.  
  702. 				pattern = @"(?:>|\b)(AUX|CLOCK\$|COM[1-8]|CON|LPT[1-8]|NUL|PRN):(?:\b|$)";
  703. 				regex = new Regex( pattern, ignorecase );
  704. 				if ( regex.IsMatch( line ) )
  705. 				{
  706. 					MatchCollection matches = regex.Matches( line );
  707. 					string device = matches[0].Groups[1].ToString( ).ToUpper( );
  708. 					message = string.Format( "Redundant colon appended to device name \"{0}\"", device );
  709. 					alltips += string.Format( "{0}Line {1,5:0}:\t{2}", newline, linenum, message );
  710. 					tipsmsg += string.Format( "{0}{1}.", newline, message );
  711. 					errinline = true;
  712. 				}
  713.  
  714. 				#endregion Devices with appended colons
  715.  
  716. 				// Obsolete commands BREAK, CTTY, KEYS, and 16-bit commands
  717. 				#region Obsolete commands
  718.  
  719. 				pattern = string.Format( @"(?:^|&|\|\()\s*({0})\b", notx32pattern );
  720. 				regex = new Regex( pattern, ignorecase );
  721. 				if ( regex.IsMatch( line ) )
  722. 				{
  723. 					MatchCollection matches = regex.Matches( line );
  724. 					foreach ( Match match in matches )
  725. 					{
  726. 						string cmd = match.Groups[1].ToString( ).ToUpper( );
  727. 						message = string.Format( "Obsolete command \"{0}\" is accepted only for MS-DOS backwards compatibility", cmd );
  728. 						alltips += string.Format( "{0}Line {1,5:0}:\t{2}", newline, linenum, message );
  729. 						tipsmsg += string.Format( "{0}{1}.", newline, message );
  730. 						obsoletecmd += 1;
  731. 					}
  732. 					errinline = true;
  733. 				}
  734.  
  735. 				pattern = string.Format( @"(?:^|&|\|\()\s*({0})\b", notx64pattern );
  736. 				regex = new Regex( pattern, ignorecase );
  737. 				if ( regex.IsMatch( line ) )
  738. 				{
  739. 					MatchCollection matches = regex.Matches( line );
  740. 					foreach ( Match match in matches )
  741. 					{
  742. 						string cmd = match.Groups[1].ToString( ).ToUpper( );
  743. 						if ( System.Environment.Is64BitOperatingSystem )
  744. 						{
  745. 							message = string.Format( "16-bit command \"{0}\" is not available in 64-bit Windows", cmd );
  746. 							allerrors += string.Format( "{0}Line {1,5:0}:\t{2}", newline, linenum, message );
  747. 							errormsg += string.Format( "{0}{1}.", newline, message );
  748. 						}
  749. 						else
  750. 						{
  751. 							message = string.Format( "16-bit command \"{0}\" will not be available in 64-bit Windows", cmd );
  752. 							alltips += string.Format( "{0}Line {1,5:0}:\t{2}", newline, linenum, message );
  753. 							tipsmsg += string.Format( "{0}{1}.", newline, message );
  754. 						}
  755. 						obsoletecmd += 1;
  756. 					}
  757. 					errinline = true;
  758. 				}
  759.  
  760. 				#endregion Obsolete commands
  761.  
  762. 				// @ all over the place
  763. 				#region Hiding command echo
  764.  
  765. 				if ( linenum > 1 )
  766. 				{
  767. 					pattern = @"^\s*@(?>!ECHO\s*(ON|OFF)\s)";
  768. 					regex = new Regex( pattern, ignorecase );
  769. 					if ( regex.IsMatch( line ) )
  770. 					{
  771. 						message = "Using multiple @s to hide command echoing is NOT recommended, as it makes debugging batch files harder. @ECHO OFF at the start of the batch file suffices to hide ALL command echoing.";
  772. 						alltips += string.Format( "{0}Line {1,5:0}:\t{2}", newline, linenum, message );
  773. 						tipsmsg += string.Format( "{0}{1}.", newline, message );
  774. 						errinline = true;
  775. 					}
  776. 				}
  777. 				#endregion Hiding command echo
  778. 			}
  779. 		}
  780.  
  781.  
  782. 		public static void CheckCommandLineArguments( string line )
  783. 		{
  784. 			if ( checkall || checkcmdargs )
  785. 			{
  786. 				string pattern;
  787. 				Regex regex;
  788.  
  789. 				// Invalid arguments for ASSOC command
  790. 				#region ASSOC command line
  791.  
  792. 				pattern = @"(^|\b|\()ASSOC(\b|$)";
  793. 				regex = new Regex( pattern, ignorecase );
  794. 				if ( regex.IsMatch( line ) )
  795. 				{
  796. 					pattern = @"(?:^|\b|\()ASSOC(\s+\.\w+(?:=[\w \.\-]{3,})?)?\s*$";
  797. 					regex = new Regex( pattern, ignorecase );
  798. 					if ( !regex.IsMatch( line ) )
  799. 					{
  800. 						pattern = @"(?:^|\b|\()ASSOC([^\n\r&|]+)";
  801. 						regex = new Regex( pattern, ignorecase );
  802. 						if ( regex.IsMatch( line ) )
  803. 						{
  804. 							string cla = regex.Match( line ).Groups[1].ToString( ).Trim( );
  805. 							if ( !string.IsNullOrWhiteSpace( cla ) && cla[0] != '/' ) // No need to display a message for switches, the check for switches will handle that
  806. 							{
  807. 								message = string.Format( "Invalid command line argument(s) \"{0}\" for ASSOC command", cla );
  808. 								allerrors += string.Format( "{0}Line {1,5:0}:\t{2}", newline, linenum, message );
  809. 								errormsg += string.Format( "{0}{1}.", newline, message );
  810. 								errinline = true;
  811. 							}
  812. 						}
  813. 					}
  814. 				}
  815.  
  816. 				#endregion ASSOC command line
  817.  
  818. 				// Invalid arguments for BREAK command
  819. 				#region BREAK command line
  820.  
  821. 				pattern = @"(^|\b|\()BREAK(\b|$)";
  822. 				regex = new Regex( pattern, ignorecase );
  823. 				if ( regex.IsMatch( line ) )
  824. 				{
  825. 					pattern = @"(?:^|\b|\()BREAK(\s+(ON|OFF))\s*$";
  826. 					regex = new Regex( pattern, ignorecase );
  827. 					if ( !regex.IsMatch( line ) )
  828. 					{
  829. 						pattern = @"(?:^|\b|\()BREAK([^\n\r&|]+)";
  830. 						regex = new Regex( pattern, ignorecase );
  831. 						if ( regex.IsMatch( line ) )
  832. 						{
  833. 							string cla = regex.Match( line ).Groups[1].ToString( ).Trim( );
  834. 							if ( !String.IsNullOrWhiteSpace( cla ) && cla[0] != '/' ) // No need to display a message for switches, the check for switches will handle that
  835. 							{
  836. 								message = string.Format( "Invalid command line argument \"{0}\" for BREAK command, the only arguments accepted are ON and OFF", cla );
  837. 								allerrors += string.Format( "{0}Line {1,5:0}:\t{2}", newline, linenum, message );
  838. 								errormsg += string.Format( "{0}{1}.", newline, message );
  839. 								errinline = true;
  840. 							}
  841. 						}
  842. 					}
  843. 					message = "The BREAK command is available for backward compatibility only, it does not DO anything in Windows";
  844. 					alltips += string.Format( "{0}Line {1,5:0}:\t{2}", newline, linenum, message );
  845. 					tipsmsg += string.Format( "{0}{1}.", newline, message );
  846. 					errinline = true;
  847. 				}
  848.  
  849. 				#endregion BREAK command line
  850.  
  851. 				// Invalid arguments for CALL command
  852. 				#region CALL command line
  853.  
  854. 				pattern = @"(?:^|\b|\()CALL\s+(\""?)([^\n\r\s\""]+)(\1\b|$)";
  855. 				regex = new Regex( pattern, ignorecase );
  856. 				if ( regex.IsMatch( line ) )
  857. 				{
  858. 					bool callerr = false;
  859. 					string cla = regex.Match( line ).Groups[2].ToString( );
  860. 					if ( string.IsNullOrWhiteSpace( cla ) )
  861. 					{
  862. 						callerr = true;
  863. 					}
  864. 					else
  865. 					{
  866. 						if ( cla[0] != ':' ) // CALLs to subroutine labels will be handled later
  867. 						{
  868. 							pattern = string.Format( @"^({0})$", intcmdpattern );
  869. 							regex = new Regex( pattern, ignorecase );
  870. 							if ( !regex.IsMatch( cla ) )
  871. 							{
  872. 								if ( string.IsNullOrWhiteSpace( Which( cla ) ) )
  873. 								{
  874. 									callerr = true;
  875. 								}
  876. 							}
  877. 						}
  878. 					}
  879. 					if ( callerr )
  880. 					{
  881. 						message = string.Format( "Invalid command line argument \"{0}\" for CALL command: no matching internal command, nor external command was found", cla );
  882. 						allerrors += string.Format( "{0}Line {1,5:0}:\t{2}", newline, linenum, message );
  883. 						errormsg += string.Format( "{0}{1}.", newline, message );
  884. 						errinline = true;
  885. 					}
  886. 				}
  887.  
  888. 				#endregion CALL command line
  889.  
  890. 				// Invalid switch for CD command
  891. 				#region CD switches
  892.  
  893. 				pattern = @"(?:^|\b|\()(CD|CHDIR)\s+(/[^D][^\n\r\s/]*)";
  894. 				regex = new Regex( pattern, ignorecase );
  895. 				if ( regex.IsMatch( line ) )
  896. 				{
  897. 					string cmd = regex.Match( line ).Groups[1].ToString( );
  898. 					string cls = regex.Match( line ).Groups[2].ToString( );
  899. 					message = string.Format( "Invalid command line switch \"{0}\" for {1} command, the only accepted switch is /D", cls, cmd );
  900. 					allerrors += string.Format( "{0}Line {1,5:0}:\t{2}", newline, linenum, message );
  901. 					errormsg += string.Format( "{0}{1}.", newline, message );
  902. 					errinline = true;
  903. 				}
  904.  
  905. 				#endregion CD switches
  906.  
  907. 				// Invalid argument for DATE command
  908. 				#region DATE arguments
  909.  
  910. 				pattern = @"(^|\b|\()(?<![%!])DATE(?>![%!:])(\b|$)";
  911. 				regex = new Regex( pattern, ignorecase );
  912. 				if ( regex.IsMatch( line ) )
  913. 				{
  914. 					pattern = @"(?:^|\b|\()DATE\s+(/[^T][^\^\n\r\s&|\)']*|/T[^\^\n\r\s&|\)']+)";
  915. 					regex = new Regex( pattern, ignorecase );
  916. 					if ( regex.IsMatch( line ) )
  917. 					{
  918. 						string cls = regex.Match( line ).Groups[1].ToString( );
  919. 						message = string.Format( "Invalid command line switch \"{0}\" for DATE command, the only valid switch is /T", cls );
  920. 						allerrors += string.Format( "{0}Line {1,5:0}:\t{2}", newline, linenum, message );
  921. 						errormsg += string.Format( "{0}{1}.", newline, message );
  922. 						errinline = true;
  923. 					}
  924. 					else
  925. 					{
  926. 						pattern = @"(?:^|\b|\()DATE\s+(/T|(%|!)[\w_-]{1,16}\2|[\d/-]{10})";
  927. 						regex = new Regex( pattern, ignorecase );
  928. 						if ( !regex.IsMatch( line ) )
  929. 						{
  930. 							string cls = regex.Match( line ).Groups[1].ToString( );
  931. 							message = string.Format( "Invalid command line argument \"{0}\" for DATE command, this should be a date", cls );
  932. 							allerrors += string.Format( "{0}Line {1,5:0}:\t{2}", newline, linenum, message );
  933. 							errormsg += string.Format( "{0}{1}.", newline, message );
  934. 							errinline = true;
  935. 						}
  936. 					}
  937. 				}
  938.  
  939. 				#endregion DATE arguments
  940.  
  941. 				// Invalid switch for EXIT command
  942. 				#region EXIT switches
  943.  
  944. 				pattern = @"(?:^|\b|\()EXIT\s+(/[^B][^\n\r\s/]*)";
  945. 				regex = new Regex( pattern, ignorecase );
  946. 				if ( regex.IsMatch( line ) )
  947. 				{
  948. 					string cls = regex.Match( line ).Groups[1].ToString( );
  949. 					message = string.Format( "Invalid command line switch \"{0}\" for EXIT command, the only accepted switch is /B", cls );
  950. 					allerrors += string.Format( "{0}Line {1,5:0}:\t{2}", newline, linenum, message );
  951. 					errormsg += string.Format( "{0}{1}.", newline, message );
  952. 					errinline = true;
  953. 				}
  954.  
  955. 				#endregion EXIT switches
  956.  
  957. 				// Invalid switch for FOR command
  958. 				#region FOR switches
  959.  
  960. 				pattern = @"(?:^|\b|\()FOR\s+(/[^DFLR][^\n\r\s=/]*)";
  961. 				regex = new Regex( pattern, ignorecase );
  962. 				if ( regex.IsMatch( line ) )
  963. 				{
  964. 					message = string.Format( "Invalid command line switch \"{0}\" for FOR command, accepted switches are /D, /F, /L and /R", regex.Match( line ).Groups[1].ToString( ) );
  965. 					allerrors += string.Format( "{0}Line {1,5:0}:\t{2}", newline, linenum, message );
  966. 					errormsg += string.Format( "{0}{1}.", newline, message );
  967. 					errinline = true;
  968. 				}
  969.  
  970. 				#endregion FOR switches
  971.  
  972. 				// Invalid arguments for FTYPE command
  973. 				#region FTYPE command line
  974.  
  975. 				pattern = @"(^|\b|\()FTYPE(\b|$)";
  976. 				regex = new Regex( pattern, ignorecase );
  977. 				if ( regex.IsMatch( line ) )
  978. 				{
  979. 					pattern = @"(?:^|\b|\()FTYPE(\s+[^\.\n\r&|][\w \.\-]{2,}(=[\w: \.\\\-\(\)]+)?)?\s*$";
  980. 					regex = new Regex( pattern, ignorecase );
  981. 					if ( !regex.IsMatch( line ) )
  982. 					{
  983. 						pattern = @"(?:^|\b|\()FTYPE\s+([^\n\r&|]+)";
  984. 						regex = new Regex( pattern, ignorecase );
  985. 						if ( regex.IsMatch( line ) )
  986. 						{
  987. 							string cla = regex.Match( line ).Groups[1].ToString( ).Trim( );
  988. 							if ( !String.IsNullOrWhiteSpace( cla ) && cla[0] != '/' ) // No need to display a message for switches, the check for switches will handle that
  989. 							{
  990. 								message = string.Format( "Invalid command line argument(s) \"{0}\" for FTYPE command", cla );
  991. 								allerrors += string.Format( "{0}Line {1,5:0}:\t{2}", newline, linenum, message );
  992. 								errormsg += string.Format( "{0}{1}.", newline, message );
  993. 								errinline = true;
  994. 							}
  995. 						}
  996. 					}
  997. 				}
  998.  
  999. 				#endregion FTYPE command line
  1000.  
  1001. 				// Invalid switch for IF command
  1002. 				#region IF switches
  1003.  
  1004. 				pattern = @"(?:^|\b|\()(?<!/)IF\s+(/[^I][^\n\r\s/]*)";
  1005. 				regex = new Regex( pattern, ignorecase );
  1006. 				if ( regex.IsMatch( line ) )
  1007. 				{
  1008. 					string cls = regex.Match( line ).Groups[1].ToString( );
  1009. 					message = string.Format( "Invalid command line switch \"{0}\" for IF command, the only accepted switch is /I", cls );
  1010. 					allerrors += string.Format( "{0}Line {1,5:0}:\t{2}", newline, linenum, message );
  1011. 					errormsg += string.Format( "{0}{1}.", newline, message );
  1012. 					errinline = true;
  1013. 				}
  1014.  
  1015. 				#endregion IF switches
  1016.  
  1017. 				// Invalid switch for SET command
  1018. 				#region SET switches
  1019.  
  1020. 				pattern = @"(?:^|\b|\()SET\s+(/[^AP][^\n\r\s=/]*)";
  1021. 				regex = new Regex( pattern, ignorecase );
  1022. 				if ( regex.IsMatch( line ) )
  1023. 				{
  1024. 					message = string.Format( "Invalid command line switch \"{0}\" for SET command, accepted switches are /A and /P", regex.Match( line ).Groups[1].ToString( ) );
  1025. 					allerrors += string.Format( "{0}Line {1,5:0}:\t{2}", newline, linenum, message );
  1026. 					errormsg += string.Format( "{0}{1}.", newline, message );
  1027. 					errinline = true;
  1028. 				}
  1029.  
  1030. 				#endregion SET switches
  1031.  
  1032. 				// Invalid switch for SHIFT command
  1033. 				#region SHIFT switches
  1034.  
  1035. 				pattern = @"(?:^|\b|\()SHIFT\s+(/[^0-8][^\n\r\s/]*)";
  1036. 				regex = new Regex( pattern, ignorecase );
  1037. 				if ( regex.IsMatch( line ) )
  1038. 				{
  1039. 					string cls = regex.Match( line ).Groups[1].ToString( );
  1040. 					message = string.Format( "Invalid command line switch \"{0}\" for SHIFT command, the only accepted switch is a forward slash followed by a number from 0 to 8", cls );
  1041. 					allerrors += string.Format( "{0}Line {1,5:0}:\t{2}", newline, linenum, message );
  1042. 					errormsg += string.Format( "{0}{1}.", newline, message );
  1043. 					errinline = true;
  1044. 				}
  1045.  
  1046. 				#endregion SHIFT switches
  1047.  
  1048. 				// Invalid argument for TIME command
  1049. 				#region TIME arguments
  1050.  
  1051. 				pattern = @"(^|\b|\()(?<![%!])TIME(?>![%!:])(\b|$)";
  1052. 				regex = new Regex( pattern, ignorecase );
  1053. 				if ( regex.IsMatch( line ) )
  1054. 				{
  1055. 					pattern = @"(?:^|\b|\()TIME\s+(/[^T][^\^\n\r\s&|\)']*|/T[^\^\n\r\s&|\)']+)";
  1056. 					regex = new Regex( pattern, ignorecase );
  1057. 					if ( regex.IsMatch( line ) )
  1058. 					{
  1059. 						string cls = regex.Match( line ).Groups[1].ToString( );
  1060. 						message = string.Format( "Invalid command line switch \"{0}\" for TIME command, the only valid switch is /T", cls );
  1061. 						allerrors += string.Format( "{0}Line {1,5:0}:\t{2}", newline, linenum, message );
  1062. 						errormsg += string.Format( "{0}{1}.", newline, message );
  1063. 						errinline = true;
  1064. 					}
  1065. 					else
  1066. 					{
  1067. 						pattern = @"(?:^|\b|\()TIME\s+(/T|(%|!)[\w_-]{1,16}\2|[\d:\.,AMP\s]{4,11})";
  1068. 						regex = new Regex( pattern, ignorecase );
  1069. 						if ( !regex.IsMatch( line ) )
  1070. 						{
  1071. 							string cls = regex.Match( line ).Groups[1].ToString( );
  1072. 							message = string.Format( "Invalid command line argument \"{0}\" for TIME command, this should be a time value", cls );
  1073. 							allerrors += string.Format( "{0}Line {1,5:0}:\t{2}", newline, linenum, message );
  1074. 							errormsg += string.Format( "{0}{1}.", newline, message );
  1075. 							errinline = true;
  1076. 						}
  1077. 					}
  1078. 				}
  1079.  
  1080. 				#endregion TIME arguments
  1081.  
  1082. 				// Switches not allowed for ASSOC, CALL, COLOR, FTYPE, GOTO, MD, MKDIR, PUSHD, SETLOCAL
  1083. 				#region Switches not allowed
  1084.  
  1085. 				pattern = @"(?:^|\b|\()(ASSOC|CALL|COLOR|FTYPE|GOTO|MD|MKDIR|PUSHD|SETLOCAL)\s+(/[^\n\r\s=/]*)";
  1086. 				regex = new Regex( pattern, ignorecase );
  1087. 				if ( regex.IsMatch( line ) )
  1088. 				{
  1089. 					string cmd = regex.Match( line ).Groups[1].ToString( ).ToUpper( );
  1090. 					string cls = regex.Match( line ).Groups[2].ToString( );
  1091. 					message = string.Format( "Switch \"{1}\" for {0} command is not valid, as {0} does not accept switches at all", cmd, cls );
  1092. 					allerrors += string.Format( "{0}Line {1,5:0}:\t{2}", newline, linenum, message );
  1093. 					errormsg += string.Format( "{0}{1}.", newline, message );
  1094. 					errinline = true;
  1095. 				}
  1096.  
  1097. 				#endregion Switches not allowed
  1098.  
  1099. 				// Arguments not required for CLS, ENDLOCAL, PAUSE, POPD, VER
  1100. 				#region Arguments not required
  1101.  
  1102. 				pattern = @"(?:^|\b|\()(CLS|ENDLOCAL|PAUSE|POPD|VER)\s+([^\n\r&|>]+)";
  1103. 				regex = new Regex( pattern, ignorecase );
  1104. 				if ( regex.IsMatch( line ) )
  1105. 				{
  1106. 					string cmd = regex.Match( line ).Groups[1].ToString( ).ToUpper( );
  1107. 					string cla = regex.Match( line ).Groups[2].ToString( );
  1108. 					message = string.Format( "Argument \"{1}\" for {0} command is not valid, as {0} does not accept arguments at all", cmd, cla );
  1109. 					allerrors += string.Format( "{0}Line {1,5:0}:\t{2}", newline, linenum, message );
  1110. 					errormsg += string.Format( "{0}{1}.", newline, message );
  1111. 					errinline = true;
  1112. 				}
  1113.  
  1114. 				#endregion Arguments not required
  1115. 			}
  1116. 		}
  1117.  
  1118.  
  1119. 		public static void CheckCommonMistakes( string line )
  1120. 		{
  1121. 			if ( checkall || checkcommerr )
  1122. 			{
  1123. 				string pattern;
  1124. 				Regex regex;
  1125.  
  1126. 				// IF [NOT] EXISTS or IF [NOT] EXIT or IF [NOT] EXITS instead of IF EXIST
  1127. 				#region IF EXIST
  1128.  
  1129. 				pattern = @"\bIF(\s+NOT)?\s+EXI(STS|T|TS)\s";
  1130. 				regex = new Regex( pattern, ignorecase );
  1131. 				if ( regex.IsMatch( line ) )
  1132. 				{
  1133. 					//message = "Found \"IF EXISTS ...\" (note the incorrect trailing \"S\"); use \"IF EXIST ...\" (without trailing \"S\") instead";
  1134. 					MatchCollection matches = regex.Matches( line );
  1135. 					string matchall = matches[0].ToString( ).ToUpper( );
  1136. 					string matchnot = matches[0].Groups[1].ToString( ).ToUpper( );
  1137. 					string matchend = matches[0].Groups[2].ToString( ).ToUpper( );
  1138. 					switch ( matchend )
  1139. 					{
  1140. 						case "STS":
  1141. 							message = string.Format( "Found \"{0} ...\" (note the incorrect trailing \"S\"); use \"IF{1} EXIST ...\" (without trailing \"S\") instead", matchall, matchnot );
  1142. 							break;
  1143. 						case "T":
  1144. 							message = string.Format( "Found \"{0} ...\" (note the missing \"S\"); use \"IF{1} EXIST ...\" (with \"S\") instead", matchall, matchnot );
  1145. 							break;
  1146. 						case "TS":
  1147. 							message = string.Format( "Found \"{0} ...\" (note the swapped \"S\" and \"T\"); use \"IF{1} EXIST ...\" (\"S\" first, followed by \"T\") instead", matchall, matchnot );
  1148. 							break;
  1149. 					}
  1150. 					allerrors += string.Format( "{0}Line {1,5:0}:\t{2}", newline, linenum, message );
  1151. 					errormsg += string.Format( "{0}{1}.", newline, message );
  1152. 					errinline = true;
  1153. 				}
  1154.  
  1155. 				#endregion IF EXIST
  1156.  
  1157. 				// Common typos for %AllUsersProfile% variable
  1158. 				#region AllUsersProfile
  1159.  
  1160. 				pattern = @"(%|!)(A+L+USERPROFILES?)(\1)";
  1161. 				regex = new Regex( pattern, ignorecase );
  1162. 				if ( regex.IsMatch( line ) )
  1163. 				{
  1164. 					MatchCollection matches = regex.Matches( line );
  1165. 					foreach ( Match match in matches )
  1166. 					{
  1167. 						string wrong = match.Groups[2].ToString( ).ToUpper( );
  1168. 						string varchr = match.Groups[1].ToString( );
  1169. 						message = string.Format( "Possible typo: the system variable name is spelled \"{0}ALLUSERSPROFILE{0}\", not \"{0}{1}{0}\"", varchr, wrong );
  1170. 						allwarnings += string.Format( "{0}Line {1,5:0}:\t{2}", newline, linenum, message );
  1171. 						warnmsg += string.Format( "{0}{1}.", newline, message );
  1172. 						errinline = true;
  1173. 					}
  1174. 				}
  1175.  
  1176. 				#endregion AllUsersProfile
  1177.  
  1178. 				// IF [NOT] ERRORLEVEL 0
  1179. 				#region IF ERRORLEVEL 0
  1180.  
  1181. 				pattern = @"(^|\b|\()IF\s+ERRORLEVEL\s+0\s";
  1182. 				regex = new Regex( pattern, ignorecase );
  1183. 				if ( regex.IsMatch( line ) )
  1184. 				{
  1185. 					message = "IF ERRORLEVEL 0 tests if the errorlevel is greater than or equal to 0, so for most native commands it ALWAYS returns true; use either IF NOT ERRORLEVEL 1 or IF %ErrorLevel% EQU 0 instead";
  1186. 					allbadpr += string.Format( "{0}Line {1,5:0}:\t{2}", newline, linenum, message );
  1187. 					badprmsg += string.Format( "{0}{1}.", newline, message );
  1188. 					errinline = true;
  1189. 					rc = linenum;
  1190. 				}
  1191.  
  1192. 				pattern = @"(^|\b|\()IF\s+NOT\s+ERRORLEVEL\s+0\s";
  1193. 				regex = new Regex( pattern, ignorecase );
  1194. 				if ( regex.IsMatch( line ) )
  1195. 				{
  1196. 					message = "IF NOT ERRORLEVEL 0 tests if the errorlevel is less than 0, so for most native commands it always ALWAYS returns false; use either IF ERRORLEVEL 1 or IF %ErrorLevel% NEQ 0 instead";
  1197. 					allbadpr += string.Format( "{0}Line {1,5:0}:\t{2}", newline, linenum, message );
  1198. 					badprmsg += string.Format( "{0}{1}.", newline, message );
  1199. 					errinline = true;
  1200. 					rc = linenum;
  1201. 				}
  1202.  
  1203. 				#endregion IF ERRORLEVEL 0
  1204.  
  1205. 				// :: inside code blocks
  1206. 				#region Double colons in code blocks
  1207.  
  1208. 				// check if :: is used inside a code block, but skip REM or ECHO followed by ::
  1209. 				bool codeblock = ( parenthesis > 0 );
  1210. 				bool dblcolcmnt = ( line.IndexOf( "::" ) > line.IndexOfAny( "(&|".ToCharArray( ) ) );
  1211. 				bool echoedcmnt = new Regex( @"^([^:]*|([^:]*:[^:]+)*)ECHO\b[^\n\r&\|\>]*::", ignorecase ).IsMatch( line ); // Skip if ECHO is found before :: and no redirection in between
  1212. 				bool remcomment = new Regex( @"^([^:]*|([^:]*:[^:]+)*)REM\b[^\n\r&]*::", ignorecase ).IsMatch( line ); // Skip if REM is found before :: and no ampersand in between
  1213. 				if ( dblcolcmnt && codeblock && !remcomment && !echoedcmnt )
  1214. 				{
  1215. 					message = "Found \"::\" inside a code block (i.e. in parenthesis); use \"REM\" instead";
  1216. 					allerrors += string.Format( "{0}Line {1,5:0}:\t{2}", newline, linenum, message );
  1217. 					errormsg += string.Format( "{0}{1}.", newline, message );
  1218. 					errinline = true;
  1219. 					rc = linenum;
  1220. 				}
  1221.  
  1222. 				#endregion Double colons in code blocks
  1223.  
  1224. 				// CALLs to undefined subroutines
  1225. 				#region CALLs
  1226.  
  1227. 				if ( new Regex( @"(?:^|\s)CALL\s+:", ignorecase ).IsMatch( line ) )
  1228. 				// if ( new Regex( @"(?:^|\b)CALL\s+:", ignorecase ).IsMatch( line ) )
  1229. 				{
  1230. 					//MatchCollection matches = new Regex( @"(?:^|\b)CALL\s+:([\w\.\[\]\/+\-\(\)\{\}]{1,127})(?=$|\s|\)|&|\|)", ignorecase ).Matches( line );
  1231. 					MatchCollection matches = new Regex( @"(?:^|\s)CALL\s+:([\w\.\[\]\/+\-\(\)\{\}]{1,127})(?=$|\s|\)|&|\|)", ignorecase ).Matches( line );
  1232. 					foreach ( Match match in matches )
  1233. 					{
  1234. 						string label = match.Groups[1].ToString( );
  1235. 						if ( !labels.Keys.Contains( label ) )
  1236. 						{
  1237. 							callerrors.Add( label );
  1238. 							message = string.Format( "A CALL is made to a subroutine named \"{0}\" but the subroutine was not found", label );
  1239. 							allerrors += string.Format( "{0}Line {1,5:0}:\t{2}", newline, linenum, message );
  1240. 							errormsg += string.Format( "{0}{1}.", newline, message );
  1241. 							errinline = true;
  1242. 						}
  1243. 					}
  1244. 				}
  1245.  
  1246. 				#endregion CALLs
  1247.  
  1248. 				// GOTOs to undefined labels
  1249. 				#region GOTOs
  1250.  
  1251. 				if ( new Regex( @"(?:^|\b|\s|\()GOTO\s", ignorecase ).IsMatch( line ) )
  1252. 				{
  1253. 					MatchCollection matches = new Regex( @"(?:^|\b|\s|\()GOTO\s+:?([\w\.\[\]/+\-]{1,127})[\)&|\s]*", ignorecase ).Matches( line );
  1254. 					foreach ( Match match in matches )
  1255. 					{
  1256. 						string label = match.Groups[1].ToString( );
  1257. 						if ( label.ToUpper( ) != "EOF" && !labels.Keys.Contains( label ) )
  1258. 						{
  1259. 							gotoerrors.Add( label );
  1260. 							message = string.Format( "A jump (GOTO) to a label named \"{0}\" was detected, but the label was not found", label );
  1261. 							allerrors += string.Format( "{0}Line {1,5:0}:\t{2}", newline, linenum, message );
  1262. 							errormsg += string.Format( "{0}{1}.", newline, message );
  1263. 							errinline = true;
  1264. 						}
  1265. 					}
  1266. 				}
  1267.  
  1268. 				#endregion GOTOs
  1269.  
  1270. 				// Redirected Standard Error typos
  1271. 				#region Redirected Standard Error
  1272.  
  1273. 				pattern = @"\s(2(?:>>|<+|\s+)&\s*1)(?:\b|$)";
  1274. 				regex = new Regex( pattern, ignorecase );
  1275. 				if ( regex.IsMatch( line ) )
  1276. 				{
  1277. 					MatchCollection matches = regex.Matches( line );
  1278. 					string redirect = matches[0].Groups[1].ToString( ).ToUpper( );
  1279. 					message = string.Format( "Incorrect redirection of Standard Error to Standard Output: \"{0}\"", redirect );
  1280. 					allerrors += string.Format( "{0}Line {1,5:0}:\t{2}", newline, linenum, message );
  1281. 					errormsg += string.Format( "{0}{1}.", newline, message );
  1282. 					errinline = true;
  1283. 				}
  1284.  
  1285. 				pattern = @"\s(1(?:>>|<+|\s+)&\s*2)(?:\b|$)";
  1286. 				regex = new Regex( pattern, ignorecase );
  1287. 				if ( regex.IsMatch( line ) )
  1288. 				{
  1289. 					MatchCollection matches = regex.Matches( line );
  1290. 					string redirect = matches[0].Groups[1].ToString( ).ToUpper( );
  1291. 					message = string.Format( "Incorrect redirection of Standard Output to Standard Error: \"{0}\"", redirect );
  1292. 					allerrors += string.Format( "{0}Line {1,5:0}:\t{2}", newline, linenum, message );
  1293. 					errormsg += string.Format( "{0}{1}.", newline, message );
  1294. 					errinline = true;
  1295. 				}
  1296.  
  1297. 				pattern = @"2>&1[ \t""]*[^$&\|\)]";
  1298. 				regex = new Regex( pattern, ignorecase );
  1299. 				if ( regex.IsMatch( line ) )
  1300. 				{
  1301. 					message = "Redirection of Standard Error to Standard Output (2>&1) should always be placed at the end of the command";
  1302. 					allbadpr += string.Format( "{0}Line {1,5:0}:\t{2}", newline, linenum, message );
  1303. 					badprmsg += string.Format( "{0}{1}.", newline, message );
  1304. 					errinline = true;
  1305. 				}
  1306.  
  1307. 				#endregion Redirected Standard Error
  1308.  
  1309. 				// Unescaped redirection in FOR /F loops
  1310. 				#region Unescaped redirection in FOR /F loops
  1311.  
  1312. 				pattern = @"FOR\s+/F\s+(""((delims|eol|skip|tokens)=[^ ""]*\s*|usebackq\s*)*"")?\s*%%[A-Z?]\s+IN\s+\('[^\^']*[\&\<\>\|]";
  1313. 				regex = new Regex( pattern, ignorecase );
  1314. 				if ( regex.IsMatch( line ) )
  1315. 				{
  1316. 					message = "Unescaped redirection symbol(s) in a FOR /F loop";
  1317. 					allerrors += string.Format( "{0}Line {1,5:0}:\t{2}", newline, linenum, message );
  1318. 					errormsg += string.Format( "{0}{1}.", newline, message );
  1319. 					errinline = true;
  1320. 				}
  1321.  
  1322. 				#endregion Unescaped redirection in FOR /F loops
  1323.  
  1324. 				// Unescaped ECHOed parenthesis inside code block
  1325. 				#region Unescaped ECHOed parenthesis inside code block
  1326.  
  1327. 				if ( parenthesis > 0 ) // if we're inside a code block...
  1328. 				{
  1329. 					pattern = @"(^|\b)ECHO(?=\.|\s)[^&\|\n\r\(\)^]*[^^][\(\)]";
  1330. 				}
  1331. 				else
  1332. 				{
  1333. 					pattern = @"\(\s*ECHO(?=\.|\s)[^&\|\n\r\(\)^]*[^^][\(\)]";
  1334. 				}
  1335. 				regex = new Regex( pattern, ignorecase );
  1336. 				if ( regex.IsMatch( line ) )
  1337. 				{
  1338. 					message = "Unescaped ECHOed parenthesis inside a code block";
  1339. 					allerrors += string.Format( "{0}Line {1,5:0}:\t{2}", newline, linenum, message );
  1340. 					errormsg += string.Format( "{0}{1}.", newline, message );
  1341. 					errinline = true;
  1342. 				}
  1343.  
  1344. 				#endregion Unescaped ECHOed parenthesis inside code block
  1345.  
  1346. 				// Delayed variables with delayed variable expansion disabled vv.
  1347. 				#region Delayed variables without delayed expansion
  1348.  
  1349. 				if ( delayedvarexp )
  1350. 				{
  1351. 					pattern = @"%\w{1,16}%";
  1352. 					regex = new Regex( pattern );
  1353. 					if ( regex.IsMatch( line ) )
  1354. 					{
  1355. 						string envvar = regex.Match( line ).Value.ToUpper( );
  1356. 						string varname = envvar.Substring( 1, envvar.Length - 2 );
  1357. 						// Skip warning if we are dealing with a (dynamic) system variable
  1358. 						pattern = string.Format( @"^({0}|{1})$", dynvarpattern, sysvarpattern );
  1359. 						if ( !new Regex( pattern, ignorecase ).IsMatch( varname ) )
  1360. 						{
  1361. 							message = string.Format( "Variable \"{0}\" found with delayed variable expansion enabled; shouldn't that be \"!{1}!\" instead?", envvar, varname );
  1362. 							// Skip warning if line is a comment
  1363. 							if ( !new Regex( @"(^|&)\s*(REM($|\s)|::)", ignorecase ).IsMatch( line ) )
  1364. 							{
  1365. 								allwarnings += string.Format( "{0}Line {1,5:0}:\t{2}", newline, linenum, message );
  1366. 								warnmsg += string.Format( "{0}{1}.", newline, message );
  1367. 								errinline = true;
  1368. 							}
  1369. 						}
  1370. 					}
  1371. 				}
  1372. 				else
  1373. 				{
  1374. 					pattern = @"!\w{1,16}!";
  1375. 					regex = new Regex( pattern );
  1376. 					if ( regex.IsMatch( line ) )
  1377. 					{
  1378. 						// Skip warning if line is a comment
  1379. 						if ( !new Regex( @"^\s*(REM($|\s)|::)", ignorecase ).IsMatch( line ) )
  1380. 						{
  1381. 							if ( delayedvarglobal )
  1382. 							{
  1383. 								message = string.Format( "\"{0}\" looks like a delayed variable, check if delayed variable expansion is enabled", regex.Match( line ).Value.ToUpper( ) );
  1384. 								allbadpr += string.Format( "{0}Line {1,5:0}:\t{2}", newline, linenum, message );
  1385. 								badprmsg += string.Format( "{0}{1}.", newline, message );
  1386. 							}
  1387. 							else
  1388. 							{
  1389. 								message = string.Format( "\"{0}\" looks like a delayed variable, but delayed variable expansion is not enabled", regex.Match( line ).Value.ToUpper( ) );
  1390. 								allerrors += string.Format( "{0}Line {1,5:0}:\t{2}", newline, linenum, message );
  1391. 								errormsg += string.Format( "{0}{1}.", newline, message );
  1392. 							}
  1393. 							errinline = true;
  1394. 						}
  1395. 					}
  1396. 				}
  1397.  
  1398. 				#endregion Delayed variables without delayed expansion
  1399.  
  1400. 				// Excess number of delimiters in FOR /F loop
  1401. 				#region Too many delimiters
  1402.  
  1403. 				pattern = @"(?<!\w)FOR\s+/F\s+\""(?:[^""]+\s)?delims=([^\s\)\""]{5,})(?:\s[^""]+)?\""";
  1404. 				regex = new Regex( pattern, ignorecase );
  1405. 				if ( regex.IsMatch( line ) )
  1406. 				{
  1407. 					string delims = regex.Match( line ).Groups[1].ToString( );
  1408. 					List<char> nodups = new List<char>( );
  1409. 					foreach ( char del in delims )
  1410. 					{
  1411. 						if ( !nodups.Contains( del ) )
  1412. 						{
  1413. 							nodups.Add( del );
  1414. 						}
  1415. 					}
  1416. 					if ( nodups.Count == delims.Length )
  1417. 					{
  1418. 						message = string.Format( "Found {0} delimiters in a FOR /F loop: \"{1}\". Are you trying to split a string on a WORD? The value of \"delims\" is a CHARACTER array, each individual character is a delimiter", delims.Length, delims, delims.Length - nodups.Count );
  1419. 						alltips += string.Format( "{0}Line {1,5:0}:\t{2}", newline, linenum, message );
  1420. 						tipsmsg += string.Format( "{0}{1}.", newline, message );
  1421. 					}
  1422. 					else
  1423. 					{
  1424. 						message = string.Format( "Found {0} delimiters in a FOR /F loop: \"{1}\" (including {2} duplicates). Are you trying to split a string on a WORD? The value of \"delims\" is a CHARACTER array, each individual character is a delimiter", delims.Length, delims, delims.Length - nodups.Count );
  1425. 						allerrors += string.Format( "{0}Line {1,5:0}:\t{2}", newline, linenum, message );
  1426. 						errormsg += string.Format( "{0}{1}.", newline, message );
  1427. 					}
  1428. 					errinline = true;
  1429. 				}
  1430. 				else
  1431. 				{
  1432. 					pattern = @"(?<!\w)FOR\s+/F\s+\""(?:[^""]+\s)?delims=([^\s\)\""]+)(?:\s[^""]+)?\""";
  1433. 					regex = new Regex( pattern, ignorecase );
  1434. 					if ( regex.IsMatch( line ) )
  1435. 					{
  1436. 						string delims = regex.Match( line ).Groups[1].ToString( );
  1437. 						List<char> nodups = new List<char>( );
  1438. 						foreach ( char del in delims )
  1439. 						{
  1440. 							if ( !nodups.Contains( del ) )
  1441. 							{
  1442. 								nodups.Add( del );
  1443. 							}
  1444. 						}
  1445. 						if ( delims.Length > nodups.Count )
  1446. 						{
  1447. 							message = string.Format( "Found {0} duplicate delimiters (delims={1}) in a FOR /F loop", delims.Length - nodups.Count, delims );
  1448. 							allerrors += string.Format( "{0}Line {1,5:0}:\t{2}", newline, linenum, message );
  1449. 							errormsg += string.Format( "{0}{1}.", newline, message );
  1450. 							errinline = true;
  1451. 						}
  1452. 					}
  1453. 				}
  1454.  
  1455. 				#endregion Too many delimiters
  1456.  
  1457. 				// Wildcards in IF comparison
  1458. 				#region Wildcards in IF comparison
  1459.  
  1460. 				pattern = @"\bIF\s+(?:/I\s+)?(?:NOT\s+)?\""%\w{1,16}%\""\s*==\s*\""[^""\*\?]*[\*\?]";
  1461. 				regex = new Regex( pattern, ignorecase );
  1462. 				if ( regex.IsMatch( line ) )
  1463. 				{
  1464. 					message = "Wildcard character(s) found in IF comparison; IF \"%var%\"==\"literal_string\" does not accept wildcards; though asterisks and exclamation marks CAN be valid literals, make sure you did not try to apply them as wildcards";
  1465. 					alltips += string.Format( "{0}Line {1,5:0}:\t{2}", newline, linenum, message );
  1466. 					tipsmsg += string.Format( "{0}{1}.", newline, message );
  1467. 					errinline = true;
  1468. 				}
  1469.  
  1470. 				#endregion Wildcards in IF comparison
  1471.  
  1472. 				// ELSE on new line
  1473. 				#region ELSE on new line
  1474.  
  1475. 				pattern = @"(^|\n)\s*ELSE";
  1476. 				regex = new Regex( pattern, ignorecase );
  1477. 				if ( regex.IsMatch( line ) )
  1478. 				{
  1479. 					// Missing error message and incorrect results reported by Bob Berry
  1480. 					message = "ELSE should never be placed on a new line; placing ELSE on a new line will result in incorrect results without a warning or error message";
  1481. 					alltips += string.Format( "{0}Line {1,5:0}:\t{2}", newline, linenum, message );
  1482. 					tipsmsg += string.Format( "{0}{1}.", newline, message );
  1483. 					errinline = true;
  1484. 				}
  1485.  
  1486. 				#endregion ELSE on new line
  1487.  
  1488. 				// count SETLOCAL and ENDLOCAL pairs
  1489. 				#region SETLOCAL ENDLOCAL pairs
  1490.  
  1491. 				pattern = @"^\s*SETLOCAL\b";
  1492. 				regex = new Regex( pattern, ignorecase );
  1493. 				if ( regex.IsMatch( line ) )
  1494. 				{
  1495. 					setlocal += 1;
  1496. 				}
  1497. 				if ( setlocal > 1 )
  1498. 				{
  1499. 					message = "Found a nested SETLOCAL";
  1500. 					if ( allwarnings.IndexOf( message ) > -1 )
  1501. 					{
  1502. 						allwarnings += string.Format( "{0}Line {1,5:0}:\t{2}", newline, linenum, message );
  1503. 						warnmsg += string.Format( "{0}{1}.", newline, message );
  1504. 					}
  1505. 					errinline = true;
  1506. 				}
  1507.  
  1508. 				pattern = @"^\s*ENDLOCAL\b";
  1509. 				regex = new Regex( pattern, ignorecase );
  1510. 				if ( regex.IsMatch( line ) )
  1511. 				{
  1512. 					setlocal -= 1;
  1513. 				}
  1514. 				if ( setlocal < 0 )
  1515. 				{
  1516. 					message = "Number of ENDLOCALs exceeds number of SETLOCALs";
  1517. 					allwarnings += string.Format( "{0}Line {1,5:0}:\t{2}", newline, linenum, message );
  1518. 					warnmsg += string.Format( "{0}{1}.", newline, message );
  1519. 					errinline = true;
  1520. 				}
  1521.  
  1522. 				#endregion SETLOCAL ENDLOCAL pairs
  1523.  
  1524. 				// Whitespace and equal signs in IF statements
  1525. 				#region Whitespace and equal signs in IF statements
  1526.  
  1527. 				pattern = @"(^|\b)IF\s+(/I\s+)?(?:\w+|\""[^""]+\"")\s*==?\s*(?:\w+|\""[^""]+\"")\s*[\(\w]";
  1528. 				regex = new Regex( pattern, ignorecase );
  1529. 				if ( regex.IsMatch( line ) )
  1530. 				{
  1531. 					// Single equal sign
  1532. 					pattern = @"(^|\b)IF\s+(/I\s+)?(?:\w+|\""[^""]+\"")\s*=\s*(?:\w+|\""[^""]+\"")\s*[\(\w]";
  1533. 					regex = new Regex( pattern, RegexOptions.IgnoreCase );
  1534. 					if ( regex.IsMatch( line ) )
  1535. 					{
  1536. 						message = "Single equal sign in IF statement, where a double equal sign is required";
  1537. 						allerrors += string.Format( "{0}Line {1,5:0}:\t{2}", newline, linenum, message );
  1538. 						errormsg += string.Format( "{0}{1}.", newline, message );
  1539. 						errinline = true;
  1540. 					}
  1541. 					// Whitespace before code block
  1542. 					pattern = @"(?:^|\b)(IF\s+(/I\s)?\s*(\w+|\""[^""]+\"")\s*==?\s*(\w+|\""[^""]+\"")\()";
  1543. 					regex = new Regex( pattern, ignorecase );
  1544. 					if ( regex.IsMatch( line ) )
  1545. 					{
  1546. 						MatchCollection matches = regex.Matches( line );
  1547. 						string wrong = matches[0].ToString( );
  1548. 						string correct = string.Format( "IF {0}{1}=={2} (", matches[0].Groups[2].ToString( ).ToUpper( ), matches[0].Groups[3].ToString( ), matches[0].Groups[4].ToString( ) );
  1549. 						message = string.Format( "Insert whitespace after the IF comparison, before the opening parenthesis of the code block: {0}...) instead of {1}...)", correct, wrong );
  1550. 						allerrors += string.Format( "{0}Line {1,5:0}:\t{2}", newline, linenum, message );
  1551. 						errormsg += string.Format( "{0}{1}.", newline, message );
  1552. 						errinline = true;
  1553. 					}
  1554. 				}
  1555.  
  1556. 				#endregion Whitespace and equal signs in IF statements
  1557. 			}
  1558. 		}
  1559.  
  1560.  
  1561. 		public static void CheckLinefeeds( )
  1562. 		{
  1563. 			string testlinefeeds = allsrc.Replace( "\r\n", "" );
  1564. 			if ( testlinefeeds.Contains( "\r" ) || testlinefeeds.Contains( "\n" ) )
  1565. 			{
  1566. 				int linecount = 0;
  1567. 				foreach ( string line in allsrc.Split( new string[] { "\r\n" }, StringSplitOptions.None ) )
  1568. 				{
  1569. 					linecount += 1;
  1570. 					if ( line.Contains( "\r" ) || line.Contains( "\n" ) )
  1571. 					{
  1572. 						incorrectlinefeeds.Add( linecount );
  1573. 					}
  1574. 				}
  1575. 			}
  1576. 		}
  1577.  
  1578.  
  1579. 		public static void CheckUndefinedVariables( string line )
  1580. 		{
  1581. 			// Ignore if test for undefined variables is excluded
  1582. 			if ( checkundef )
  1583. 			{
  1584. 				string pattern;
  1585. 				Regex regex;
  1586.  
  1587. 				// Skip this test if variable is inside comment
  1588. 				pattern = @"^\s*(::|REM\s)";
  1589. 				regex = new Regex( pattern, ignorecase );
  1590. 				if ( !regex.IsMatch( line ) )
  1591. 				{
  1592. 					// This regex pattern catches "straight" variables as well as "nested" ones
  1593. 					pattern = @"((?<!%)%{1,2}|(?:\^!)?!)([\w\.-]{1," + maxvarlen + @"})(\1|:)";
  1594. 					regex = new Regex( pattern, ignorecase );
  1595. 					if ( regex.IsMatch( line ) )
  1596. 					{
  1597. 						MatchCollection matches = regex.Matches( line );
  1598. 						foreach ( Match match in matches )
  1599. 						{
  1600. 							string envvar = match.Groups[2].ToString( );
  1601. 							if ( !upcaseenvvars.Contains( envvar.ToUpper( ) ) )
  1602. 							{
  1603. 								if ( !envvarerrors.Contains( envvar.ToUpper( ) ) )
  1604. 								{
  1605. 									envvarerrors.Add( envvar.ToUpper( ) );
  1606. 								}
  1607. 								if ( includelocalvars || !localenvvars.Contains( envvar.ToUpper( ) ) )
  1608. 								{
  1609. 									if ( mysteryvars > 0 )
  1610. 									{
  1611. 										if ( mysteryvars == 1 )
  1612. 										{
  1613. 											message = string.Format( "No SET statement found setting variable \"{0}\"; however, a line was found setting a nested variable, which could account for this program's inability to find the SET statement for this particular variable", envvar );
  1614. 										}
  1615. 										else
  1616. 										{
  1617. 											message = string.Format( "No SET statement found setting variable \"{0}\"; however, lines were found setting nested variables, which could account for this program's inability to find the SET statement for this particular variable", envvar );
  1618. 										}
  1619. 										allbadpr += string.Format( "{0}Line {1,5:0}:\t{2}", newline, linenum, message );
  1620. 										badprmsg += string.Format( "{0}{1}.", newline, message );
  1621. 									}
  1622. 									else
  1623. 									{
  1624. 										message = string.Format( "Undefined variable \"{0}\"", envvar );
  1625. 										allerrors += string.Format( "{0}Line {1,5:0}:\t{2}", newline, linenum, message );
  1626. 										errormsg += string.Format( "{0}{1}.", newline, message );
  1627. 									}
  1628. 								}
  1629. 								else
  1630. 								{
  1631. 									message = string.Format( "Variable \"{0}\" is never set in the batch file; however, the variable IS set in this computer's local environment. Use BatCodeCheck's \"/E\" switch to include the local environment variables", envvar );
  1632. 									allbadpr += string.Format( "{0}Line {1,5:0}:\t{2}", newline, linenum, message );
  1633. 									badprmsg += string.Format( "{0}{1}.", newline, message );
  1634. 								}
  1635. 								errinline = true;
  1636. 							}
  1637. 						}
  1638. 					}
  1639. 				}
  1640. 			}
  1641. 		}
  1642.  
  1643.  
  1644. 		public static void CheckVulnerabilities( string line )
  1645. 		{
  1646. 			if ( checkall || checkvulnr )
  1647. 			{
  1648. 				string pattern;
  1649. 				Regex regex;
  1650.  
  1651. 				// Unquoted %CD% code insertion vulnerability
  1652. 				#region Code insertion
  1653. 				if ( !incomment )
  1654. 				{
  1655. 					pattern = @"[^""%!](([%!])(CD|__CD__)(:~\d+(,\d+)?|:[^\2\n\r]+=[^\2\n\r]*)?\2)";
  1656. 					regex = new Regex( pattern, ignorecase );
  1657. 					if ( regex.IsMatch( line ) )
  1658. 					{
  1659. 						string cd = regex.Match( line ).Groups[1].ToString( ).ToUpper( );
  1660. 						message = string.Format( "Unquoted {0} makes the code vulnerable to code insertion (see http://www.thesecurityfactory.be/command-injection-windows.html)", cd );
  1661. 						allbadpr += string.Format( "{0}Line {1,5:0}:\t{2}", newline, linenum, message );
  1662. 						badprmsg += string.Format( "{0}{1}.", newline, message );
  1663. 						errinline = true;
  1664. 					}
  1665. 					else
  1666. 					{
  1667. 						pattern = @"[^""](([%!])([%!])(CD|__CD__)(:~\d+(,\d+)?|:[^\2\3\n\r]+=[^\2\3\n\r]*)?\3\2)";
  1668. 						regex = new Regex( pattern, ignorecase );
  1669. 						if ( regex.IsMatch( line ) )
  1670. 						{
  1671. 							string cd = regex.Match( line ).Groups[1].ToString( ).ToUpper( );
  1672. 							message = string.Format( "Unquoted {0} makes the code vulnerable to code insertion (see http://www.thesecurityfactory.be/command-injection-windows.html)", cd );
  1673. 							allbadpr += string.Format( "{0}Line {1,5:0}:\t{2}", newline, linenum, message );
  1674. 							badprmsg += string.Format( "{0}{1}.", newline, message );
  1675. 							errinline = true;
  1676. 						}
  1677. 					}
  1678. 				}
  1679. 				#endregion Code insertion
  1680.  
  1681. 				// SET /P vulnerability
  1682. 				#region SET /P
  1683.  
  1684. 				pattern = @"(^|\b)SET\s+/P\s+""?\w{1,16}\s*=";
  1685. 				regex = new Regex( pattern, ignorecase );
  1686. 				if ( regex.IsMatch( line ) )
  1687. 				{
  1688. 					message = "SET /P could make your code vulnerable to exploits (see http://www.robvanderwoude.com/battech_inputvalidation.php#SetP)";
  1689. 					allbadpr += string.Format( "{0}Line {1,5:0}:\t{2}", newline, linenum, message );
  1690. 					badprmsg += string.Format( "{0}{1}.", newline, message );
  1691. 					errinline = true;
  1692. 				}
  1693.  
  1694.  
  1695. 				#endregion SET /P
  1696. 			}
  1697. 		}
  1698.  
  1699.  
  1700. 		public static void CloseHTML( )
  1701. 		{
  1702. 			if ( log2html )
  1703. 			{
  1704. 				htmlstream.WriteLine( "</span>" );
  1705. 				htmlstream.WriteLine( );
  1706. 				htmlstream.WriteLine( "</pre>" );
  1707. 				htmlstream.WriteLine( "</body>" );
  1708. 				htmlstream.Write( "</html>" );
  1709. 				htmlstream.Close( );
  1710. 				htmlopen = false;
  1711. 			}
  1712. 		}
  1713.  
  1714.  
  1715. 		public static void CollectEnvironmentVariables( )
  1716. 		{
  1717. 			string pattern;
  1718. 			Regex regex;
  1719.  
  1720. 			// Static system environment variables
  1721. 			foreach ( string envvar in sysvarpattern.Split( '|' ) )
  1722. 			{
  1723. 				//envvars.Add( envvar, 0 );
  1724. 				envvars[envvar] = 0;
  1725. 				upcaseenvvars.Add( envvar.ToUpper( ) );
  1726. 			}
  1727.  
  1728. 			// Dynamic system environment variables
  1729. 			foreach ( string envvar in dynvarpattern.Split( '|' ) )
  1730. 			{
  1731. 				//envvars.Add( envvar, 0 );
  1732. 				envvars[envvar] = 0;
  1733. 				upcaseenvvars.Add( envvar.ToUpper( ) );
  1734. 			}
  1735.  
  1736. 			// Environment variables SET in the source code with "plain" SET statement
  1737. 			pattern = @"(?:^|\b)SET\s+(\w{1," + maxvarlen + @"})=";
  1738. 			regex = new Regex( pattern, ignorecase );
  1739. 			if ( regex.IsMatch( allsrc_nocomments ) )
  1740. 			{
  1741. 				foreach ( Match match in regex.Matches( allsrc_nocomments ) )
  1742. 				{
  1743. 					if ( !upcaseenvvars.Contains( match.Groups[1].ToString( ).ToUpper( ) ) )
  1744. 					{
  1745. 						//envvars.Add( match.Groups[1].ToString( ), 0 );
  1746. 						envvars[match.Groups[1].ToString( )] = 0;
  1747. 						upcaseenvvars.Add( match.Groups[1].ToString( ).ToUpper( ) );
  1748. 					}
  1749. 				}
  1750. 			}
  1751.  
  1752. 			// Environment variables SET in the source code with SET /A or SET /P statement
  1753. 			pattern = @"(?:^|\b)SET\s+(?:/[AP]\s+)?""?(\w{1," + maxvarlen + @"})\s*=";
  1754. 			regex = new Regex( pattern, ignorecase );
  1755. 			if ( regex.IsMatch( allsrc_nocomments ) )
  1756. 			{
  1757. 				foreach ( Match match in regex.Matches( allsrc_nocomments ) )
  1758. 				{
  1759. 					if ( !upcaseenvvars.Contains( match.Groups[1].ToString( ).ToUpper( ) ) )
  1760. 					{
  1761. 						//envvars.Add( match.Groups[1].ToString( ), 0 );
  1762. 						envvars[match.Groups[1].ToString( )] = 0;
  1763. 						upcaseenvvars.Add( match.Groups[1].ToString( ).ToUpper( ) );
  1764. 					}
  1765. 				}
  1766. 			}
  1767.  
  1768. 			// Take into account lines like SET %%A or SET %Var%=...
  1769. 			pattern = @"(^|\b)\s*SET\s+[%!]";
  1770. 			regex = new Regex( pattern, ignorecase );
  1771. 			if ( regex.IsMatch( allsrc_nocomments ) )
  1772. 			{
  1773. 				MatchCollection matches = regex.Matches( allsrc_nocomments );
  1774. 				mysteryvars = matches.Count;
  1775. 			}
  1776.  
  1777. 			// Include local environment variables if /E command line switch was used
  1778. 			if ( includelocalvars )
  1779. 			{
  1780. 				foreach ( string envvar in Environment.GetEnvironmentVariables( ).Keys )
  1781. 				{
  1782. 					if ( !upcaseenvvars.Contains( envvar ) )
  1783. 					{
  1784. 						//envvars.Add( envvar, 0 );
  1785. 						envvars[envvar] = 0;
  1786. 						upcaseenvvars.Add( envvar.ToUpper( ) );
  1787. 					}
  1788. 				}
  1789. 			}
  1790.  
  1791. 			// Check how often a variable is used
  1792. 			// Temporarily clone the keys into a new array because we are going to modify the dictionary while iterating through its keys
  1793. 			string[] envvarskeys = envvars.Keys.ToArray( );
  1794. 			foreach ( string envvar in envvarskeys )
  1795. 			{
  1796. 				string substringpattern = @"~\-?\d+(,\d+)?";
  1797. 				string replacepattern = @"[^\1\=]{1,127}=[^\1\=]";
  1798. 				pattern = string.Format( @"(%|!){0}(\:({1}|{2}))\1", envvar, substringpattern, replacepattern );
  1799. 				regex = new Regex( pattern, ignorecase );
  1800. 				int used = regex.Matches( allsrc_nocomments ).Count;
  1801. 				envvars[envvar] = used;
  1802. 			}
  1803.  
  1804. 			// All loacal environment variables
  1805. 			foreach ( string envvar in Environment.GetEnvironmentVariables( ).Keys )
  1806. 			{
  1807. 				if ( !localenvvars.Contains( envvar.ToUpper( ) ) )
  1808. 				{
  1809. 					localenvvars.Add( envvar.ToUpper( ) );
  1810. 				}
  1811. 			}
  1812. 		}
  1813.  
  1814.  
  1815. 		public static void CollectLabels( )
  1816. 		{
  1817. 			string pattern;
  1818. 			Regex regex;
  1819.  
  1820. 			pattern = @"(?:^|\n|\r)\s*:([\w\.\[\]/+\-\(\)\{\}]{1,127})";
  1821. 			regex = new Regex( pattern );
  1822. 			if ( regex.IsMatch( allsrc_nocomments ) )
  1823. 			{
  1824. 				foreach ( Match match in regex.Matches( allsrc_nocomments ) )
  1825. 				{
  1826. 					string label = match.Groups[1].ToString( );
  1827. 					// Find the number of GOTOs to this label
  1828. 					pattern = string.Format( @"(?<=^|\s|\b|\()GOTO[ \t]+:?{0}(?=\n|\r|[ \t]+[\n\r]|\)|$)", label );
  1829. 					regex = new Regex( pattern, ignorecase );
  1830. 					int jumps = regex.Matches( allsrc_nocomments ).Count;
  1831. 					//labels.Add( label, jumps );
  1832. 					labels[label] = jumps;
  1833. 					upcaselabels.Add( label.ToUpper( ) );
  1834. 				}
  1835. 			}
  1836. 		}
  1837.  
  1838.  
  1839. 		public static void CollectSubroutines( )
  1840. 		{
  1841. 			// For each label, check if it is a subroutine label
  1842. 			foreach ( string label in labels.Keys )
  1843. 			{
  1844. 				if ( subroutines.IndexOfKey( label ) == -1 )
  1845. 				{
  1846. 					string pattern = string.Format( @"(?=\n|\r|^)\s*:{0}\s*[\n\r]+(?:[ \t]*(?:[^\:\s]|\:\:)[^\n\r]*[\n\r]+)*[ \t]*($|GOTO[ \t]*:EOF[ \t]*(?=\n|\r|\)|&|$))", label );
  1847. 					Regex regex = new Regex( pattern, ignorecase );
  1848. 					if ( regex.IsMatch( allsrc_nocomments ) )
  1849. 					{
  1850. 						MatchCollection matches = regex.Matches( allsrc_nocomments );
  1851. 						foreach ( Match match in matches )
  1852. 						{
  1853. 							string pattern2 = @"(?=\n|\r|^)\s*:([\w\.\[\]/+\-\(\)\{\}]{1,127})(?=\n|\r|\s|$)";
  1854. 							Regex regex2 = new Regex( pattern2, ignorecase );
  1855. 							string match2 = match.Groups[1].ToString( );
  1856. 							if ( !regex2.IsMatch( match2 ) )
  1857. 							{
  1858. 								//subroutines.Add( label, 0 );
  1859. 								subroutines[label] = 0;
  1860. 							}
  1861. 						}
  1862. 					}
  1863. 				}
  1864. 			}
  1865.  
  1866. 			// Find the number of CALLs to each subroutine
  1867. 			foreach ( string subroutine in subroutines.Keys )
  1868. 			{
  1869. 				string pattern = string.Format( @"(?<=^|\n|\r|\s|\b|&\()CALL[ \t]+:{{0}}(?=\n|\r|\s|\b|&|\)|$)" );
  1870. 				Regex regex = new Regex( pattern, ignorecase );
  1871. 				if ( regex.IsMatch( allsrc_nocomments ) )
  1872. 				{
  1873. 					MatchCollection matches = regex.Matches( allsrc_nocomments );
  1874. 					subroutines[subroutine] = matches.Count;
  1875. 				}
  1876. 			}
  1877. 		}
  1878.  
  1879.  
  1880. 		public static string ConsoleColor2CSSColor( ConsoleColor color )
  1881. 		{
  1882. 			return ConsoleColor2CSSColor( color.ToString( ) );
  1883. 		}
  1884.  
  1885.  
  1886. 		public static string ConsoleColor2CSSColor( string color )
  1887. 		{
  1888. 			string csscolor = color.ToLower( );
  1889. 			switch ( csscolor )
  1890. 			{
  1891. 				case "darkyellow": // darkyellow is not a valid CSS value, reported and fixed by Alfons van Zwol
  1892. 					csscolor = "#808000";
  1893. 					break;
  1894. 			}
  1895. 			return csscolor;
  1896. 		}
  1897.  
  1898.  
  1899. 		public static void CreateHTML( )
  1900. 		{
  1901. 			if ( log2html )
  1902. 			{
  1903. 				htmlstream = new StreamWriter( htmlfile );
  1904. 				htmlopen = true;
  1905. 				htmlstream.WriteLine( "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">" );
  1906. 				htmlstream.WriteLine( "<html>" );
  1907. 				htmlstream.WriteLine( "<head>" );
  1908. 				htmlstream.WriteLine( "<title>BatCodeCheck Version {0} report for {1}</title>", progver, Path.GetFileName( source ) );
  1909. 				htmlstream.WriteLine( "<style type=\"text/css\">" );
  1910. 				htmlstream.WriteLine( "body" );
  1911. 				htmlstream.WriteLine( "{" );
  1912. 				htmlstream.WriteLine( "\tbackground-color: {0};", windowbgcolor.ToString( ).ToLower( ) );
  1913. 				htmlstream.WriteLine( "\tcolor: {0};", windowfgcolor.ToString( ).ToLower( ) );
  1914. 				htmlstream.WriteLine( "}" );
  1915. 				htmlstream.WriteLine( "</style>" );
  1916. 				htmlstream.WriteLine( "</head>" );
  1917. 				htmlstream.WriteLine( "<body>" );
  1918. 				htmlstream.WriteLine( "<pre>" );
  1919. 				htmlstream.WriteLine( "<span style=\"color: white;\">Time          :  {0}", DateTime.Now.ToString( "yyyy-MM-dd HH:mm:ss" ) );
  1920. 				htmlstream.WriteLine( "Program       :  BatCodeCheck, Version {0}", progver );
  1921. 				htmlstream.WriteLine( "Arguments     :  {0}", allargs );
  1922. 				htmlstream.WriteLine( "File name     :  \"{0}\"", Path.GetFullPath( source ) );
  1923. 				htmlstream.WriteLine( "File date     :  {0}", File.GetLastWriteTime( source ).ToString( "yyyy-MM-dd HH:mm:ss" ) );
  1924. 				htmlstream.WriteLine( "File encoding :  {0}", encoding );
  1925. 				htmlstream.WriteLine( "Tests         :  {0}", checks );
  1926. 				foreach ( char check in checks )
  1927. 				{
  1928. 					string test = check.ToString( ).ToUpper( );
  1929. 					htmlstream.WriteLine( "                 {0} =  {1}", test, tests[test] );
  1930. 				}
  1931. 				if ( envvarerrors.Count > 0 )
  1932. 				{
  1933. 					htmlstream.Write( "Local environment variables are {1}", ( includelocalvars ? "included" : "ignored" ) );
  1934. 				}
  1935. 				htmlstream.WriteLine( "</span>\n" );
  1936. 			}
  1937. 		}
  1938.  
  1939.  
  1940. 		public static void DisplayLineNumber( int num )
  1941. 		{
  1942. 			if ( dispnums )
  1943. 			{
  1944. 				Console.BackgroundColor = linenumbgcolor;
  1945. 				Console.ForegroundColor = linenumfgcolor;
  1946. 				if ( num == lastline )
  1947. 				{
  1948. 					Console.Write( "{0,4} ", string.Empty );
  1949. 					WriteLineHTML( string.Format( "{0,4} ", string.Empty ) );
  1950. 				}
  1951. 				else
  1952. 				{
  1953. 					Console.Write( "{0,4} ", num );
  1954. 					WriteLineHTML( string.Format( "{0,4} ", num ) );
  1955. 				}
  1956. 				Console.ResetColor( );
  1957. 				Console.BackgroundColor = windowbgcolor;
  1958. 				Console.ForegroundColor = windowfgcolor;
  1959. 				lastline = num;
  1960. 			}
  1961. 		}
  1962.  
  1963.  
  1964. 		public static void DisplayMessagesPerLine( string line )
  1965. 		{
  1966. 			bool padline = true;
  1967. 			string blankline = new String( ' ', Console.WindowWidth );
  1968. 			string msgline;
  1969.  
  1970. 			if ( errinline )
  1971. 			{
  1972. 				if ( lasterrline != linenum )
  1973. 				{
  1974. 					lineswitherrors += 1;
  1975. 				}
  1976. 				foreach ( string error in errormsg.Split( "\n\r".ToCharArray( ) ) )
  1977. 				{
  1978. 					if ( !String.IsNullOrWhiteSpace( error ) )
  1979. 					{
  1980. 						if ( wrap )
  1981. 						{
  1982. 							msgline = WordWrap( ( error ).Replace( "\t", " ERROR:   " ), padline );
  1983. 						}
  1984. 						else
  1985. 						{
  1986. 							msgline = ( error + blankline ).Replace( "\t", " ERROR:   " ).Substring( 0, Console.WindowWidth - 1 );
  1987. 						}
  1988. 						Console.BackgroundColor = errorbgcolor;
  1989. 						Console.ForegroundColor = errorfgcolor;
  1990. 						Console.Write( msgline );
  1991. 						WriteLineHTML( msgline );
  1992. 						Console.ResetColor( );
  1993. 						Console.WriteLine( );
  1994. 					}
  1995. 				}
  1996. 				foreach ( string warning in warnmsg.Split( "\n\r".ToCharArray( ) ) )
  1997. 				{
  1998. 					if ( !String.IsNullOrWhiteSpace( warning ) )
  1999. 					{
  2000. 						if ( wrap )
  2001. 						{
  2002. 							msgline = WordWrap( ( warning ).Replace( "\t", " WARNING: " ), padline );
  2003. 						}
  2004. 						else
  2005. 						{
  2006. 							msgline = ( warning + blankline ).Replace( "\t", " WARNING: " ).Substring( 0, Console.WindowWidth - 1 );
  2007. 						}
  2008. 						Console.BackgroundColor = warningbgcolor;
  2009. 						Console.ForegroundColor = warningfgcolor;
  2010. 						Console.Write( msgline );
  2011. 						WriteLineHTML( msgline );
  2012. 						Console.ResetColor( );
  2013. 						Console.WriteLine( );
  2014. 					}
  2015. 				}
  2016. 				foreach ( string badpractice in badprmsg.Split( "\n\r".ToCharArray( ) ) )
  2017. 				{
  2018. 					if ( !String.IsNullOrWhiteSpace( badpractice ) )
  2019. 					{
  2020. 						if ( wrap )
  2021. 						{
  2022. 							msgline = WordWrap( ( badpractice ).Replace( "\t", " WARNING: " ), padline );
  2023. 						}
  2024. 						else
  2025. 						{
  2026. 							msgline = ( badpractice + blankline ).Replace( "\t", " WARNING: " ).Substring( 0, Console.WindowWidth - 1 );
  2027. 						}
  2028. 						Console.BackgroundColor = badprbgcolor;
  2029. 						Console.ForegroundColor = badprfgcolor;
  2030. 						Console.Write( msgline );
  2031. 						WriteLineHTML( msgline );
  2032. 						Console.ResetColor( );
  2033. 						Console.WriteLine( );
  2034. 					}
  2035. 				}
  2036. 				foreach ( string tip in tipsmsg.Split( "\n\r".ToCharArray( ) ) )
  2037. 				{
  2038. 					if ( !String.IsNullOrWhiteSpace( tip ) )
  2039. 					{
  2040. 						if ( wrap )
  2041. 						{
  2042. 							msgline = WordWrap( ( tip ).Replace( "\t", " TIP:     " ), padline );
  2043. 						}
  2044. 						else
  2045. 						{
  2046. 							msgline = ( tip + blankline ).Replace( "\t", " TIP:     " ).Substring( 0, Console.WindowWidth - 1 );
  2047. 						}
  2048. 						Console.BackgroundColor = tipsbgcolor;
  2049. 						Console.ForegroundColor = tipsfgcolor;
  2050. 						Console.Write( msgline );
  2051. 						WriteLineHTML( msgline );
  2052. 						Console.ResetColor( );
  2053. 						Console.WriteLine( );
  2054. 					}
  2055. 				}
  2056. 				errinline = false;
  2057. 				lasterrline = linenum;
  2058. 				rc = linenum;
  2059. 			}
  2060. 		}
  2061.  
  2062.  
  2063. 		public static void DisplayMessagesSummary( )
  2064. 		{
  2065. 			// Encoding
  2066. 			if ( encoding != "ASCII" )
  2067. 			{
  2068. 				allerrsum += string.Format( "{0}The batch file \"{1}\" is stored in {2} encoding, whereas it should be in ASCII", newline, source, encoding );
  2069. 			}
  2070. 			// Summary
  2071. 			if ( lineswitherrors == 1 )
  2072. 			{
  2073. 				allerrsum += string.Format( "{0}1   line generated a warning and should be examined", newline );
  2074. 			}
  2075. 			else if ( lineswitherrors > 1 )
  2076. 			{
  2077. 				allerrsum += string.Format( "{0}{1,-3} lines generated warnings and should be examined", newline, lineswitherrors );
  2078. 			}
  2079. 			// Incorrect linefeeds
  2080. 			if ( incorrectlinefeeds.Count == 1 )
  2081. 			{
  2082. 				allerrsum += String.Format( "{0}1   line is terminated with improper linefeeds (line {1})", newline, incorrectlinefeeds[0] );
  2083. 			}
  2084. 			if ( incorrectlinefeeds.Count > 1 )
  2085. 			{
  2086. 				allerrsum += String.Format( "{0}{1,-3} lines are terminated with improper linefeeds (lines {2})", newline, incorrectlinefeeds.Count, string.Join( ", ", incorrectlinefeeds.ToArray( ) ) );
  2087. 			}
  2088. 			// Unterminated %
  2089. 			if ( unterminatedpercentsigns == 1 )
  2090. 			{
  2091. 				allerrsum += string.Format( "{0}1   unterminated percent sign was found", newline );
  2092. 			}
  2093. 			else if ( unterminatedpercentsigns > 1 )
  2094. 			{
  2095. 				allerrsum += string.Format( "{0}{1,-3} unterminated percent signs were found", newline, unterminatedpercentsigns );
  2096. 			}
  2097. 			// Unterminated !
  2098. 			if ( unterminatedexclamations == 1 )
  2099. 			{
  2100. 				allerrsum += string.Format( "{0}1   unterminated exclamation mark was found while delayed variable expansion was enabled", newline );
  2101. 			}
  2102. 			else if ( unterminatedexclamations > 1 )
  2103. 			{
  2104. 				allerrsum += string.Format( "{0}{1,-3} unterminated exclamation marks were found while delayed variable expansion was enabled", newline, unterminatedexclamations );
  2105. 			}
  2106. 			// Unterminated "
  2107. 			if ( unterminateddoublequotes == 1 )
  2108. 			{
  2109. 				allerrsum += string.Format( "{0}1   unterminated doublequote was found", newline );
  2110. 			}
  2111. 			else if ( unterminateddoublequotes > 1 )
  2112. 			{
  2113. 				allerrsum += string.Format( "{0}{1,-3} unterminated doublequotes were found", newline, unterminateddoublequotes );
  2114. 			}
  2115. 			// Unterminated '
  2116. 			if ( unterminatedsinglequotes == 1 )
  2117. 			{
  2118. 				allerrsum += string.Format( "{0}1   unterminated singlequote was found in a FOR /F loop", newline );
  2119. 			}
  2120. 			else if ( unterminatedsinglequotes > 1 )
  2121. 			{
  2122. 				allerrsum += string.Format( "{0}{1,-3} unterminated singlequotes were found in FOR /F loops", newline, unterminatedsinglequotes );
  2123. 			}
  2124. 			// Undefined environment variables
  2125. 			string ignoredmsg = ( includelocalvars ? string.Empty : " (ignoring local environment variables)" );
  2126. 			if ( envvarerrors.Count == 1 )
  2127. 			{
  2128. 				allerrsum += string.Format( "{0}1   undefined variable was detected{1}: {2}", newline, ignoredmsg, envvarerrors[0] );
  2129. 			}
  2130. 			else if ( envvarerrors.Count > 1 )
  2131. 			{
  2132. 				allerrsum += string.Format( "{0}{1,-3} undefined variables were detected{2}: {3}", newline, envvarerrors.Count, ignoredmsg, string.Join( ", ", envvarerrors ) );
  2133. 			}
  2134. 			// Undefined labels
  2135. 			if ( gotoerrors.Count == 1 )
  2136. 			{
  2137. 				allerrsum += string.Format( "{0}1   label was missing: {1}", newline, gotoerrors[0] );
  2138. 			}
  2139. 			else if ( gotoerrors.Count > 1 )
  2140. 			{
  2141. 				allerrsum += string.Format( "{0}{1,-3} labels were missing: {2}", newline, gotoerrors.Count, string.Join( ", ", gotoerrors ) );
  2142. 			}
  2143. 			// Undefined subroutines
  2144. 			if ( callerrors.Count == 1 )
  2145. 			{
  2146. 				allerrsum += string.Format( "{0}1   subroutine label was missing (note that subroutine labels are case sensitive): {1}", newline, callerrors[0] );
  2147. 			}
  2148. 			else if ( callerrors.Count > 1 )
  2149. 			{
  2150. 				allerrsum += string.Format( "{0}{1,-3} subroutine labels were missing (note that subroutine labels are case sensitive): {2}", newline, callerrors.Count, string.Join( ", ", callerrors ) );
  2151. 			}
  2152. 			// Obsolete commands
  2153. 			if ( obsoletecmd == 1 )
  2154. 			{
  2155. 				allerrsum += string.Format( "{0}1   obsolete or 16-bit command was found", newline );
  2156. 			}
  2157. 			else if ( obsoletecmd > 1 )
  2158. 			{
  2159. 				allerrsum += string.Format( "{0}{1,-3} obsolete and/or 16-bit commands were found", newline, obsoletecmd );
  2160. 			}
  2161. 			// Unterminated parentheses
  2162. 			if ( parenthesis > 0 )
  2163. 			{
  2164. 				allerrsum += string.Format( "{0}Opening parenthesis outnumber closing parenthesis by {1}", newline, parenthesis );
  2165. 			}
  2166. 			else if ( parenthesis < 0 )
  2167. 			{
  2168. 				allerrsum += string.Format( "{0}Closing parenthesis outnumber opening parenthesis by {1}", newline, Math.Abs( parenthesis ) );
  2169. 			}
  2170. 			// Unterminated SETLOCALs
  2171. 			if ( setlocal > 0 )
  2172. 			{
  2173. 				allerrsum += string.Format( "{0}SETLOCALs outnumber ENDLOCALs by {1}", newline, setlocal );
  2174. 			}
  2175. 			else if ( setlocal < 0 )
  2176. 			{
  2177. 				allerrsum += string.Format( "{0}ENDLOCALs outnumber SETLOCALs by {1}", newline, Math.Abs( setlocal ) );
  2178. 			}
  2179.  
  2180. 			// Display warnings if applicable
  2181. 			if ( !String.IsNullOrWhiteSpace( allerrors ) )
  2182. 			{
  2183. 				allerrors = string.Format( "{0}ERRORS / VULNERABILITIES:{1}{0}", newline, allerrors );
  2184. 			}
  2185. 			if ( !String.IsNullOrWhiteSpace( allwarnings ) )
  2186. 			{
  2187. 				allerrors = string.Format( "{0}{1}WARNINGS:{2}{1}", allerrors, newline, allwarnings );
  2188. 			}
  2189. 			if ( !String.IsNullOrWhiteSpace( allbadpr ) )
  2190. 			{
  2191. 				allerrors = string.Format( "{0}{1}RISKY CODE:{2}{1}", allerrors, newline, allbadpr );
  2192. 			}
  2193. 			if ( !String.IsNullOrWhiteSpace( alltips ) )
  2194. 			{
  2195. 				allerrors = string.Format( "{0}{1}TIPS / BEST PRACTICES:{2}{1}", allerrors, newline, alltips );
  2196. 			}
  2197. 			if ( !String.IsNullOrWhiteSpace( allerrsum ) )
  2198. 			{
  2199. 				allerrors = string.Format( "{0}{1}SUMMARY:{2}{1}", allerrors, newline, allerrsum );
  2200. 			}
  2201.  
  2202. 			Console.Write( VerboseSummary( ) );
  2203.  
  2204. 			if ( !String.IsNullOrEmpty( allerrors ) )
  2205. 			{
  2206. 				allerrors += string.Format( "{0}{0}Note that some warnings are only displayed once. Correct the errors and run this test again.", newline );
  2207. 				Console.ResetColor( );
  2208. 				Console.WriteLine( newline );
  2209. 				Console.WriteLine( "Time          :  {0}", DateTime.Now.ToString( "yyyy-MM-dd HH:mm:ss" ) );
  2210. 				Console.WriteLine( "Program       :  BatCodeCheck, Version {0}", progver );
  2211. 				Console.WriteLine( "Arguments     :  {0}", allargs );
  2212. 				Console.WriteLine( "File name     :  \"{0}\"", Path.GetFullPath( source ) );
  2213. 				Console.WriteLine( "File date     :  {0}", File.GetLastWriteTime( source ).ToString( "yyyy-MM-dd HH:mm:ss" ) );
  2214. 				Console.WriteLine( "File encoding :  {0}", encoding );
  2215. 				Console.WriteLine( "Tests         :  {0}", checks );
  2216. 				foreach ( char check in checks )
  2217. 				{
  2218. 					string test = check.ToString( ).ToUpper( );
  2219. 					Console.WriteLine( "                 {0} =  {1}", test, tests[test] );
  2220. 				}
  2221. 				if ( envvarerrors.Count > 0 )
  2222. 				{
  2223. 					Console.WriteLine( "Local environment variables are {0}", ( includelocalvars ? "included" : "ignored" ) );
  2224. 				}
  2225. 				Console.WriteLine( );
  2226. 				Console.BackgroundColor = reportbgcolor;
  2227. 				Console.ForegroundColor = reportfgcolor;
  2228. 				if ( wrap )
  2229. 				{
  2230. 					string[] all_errors = allerrors.Split( "\n\r".ToCharArray( ) );
  2231. 					foreach ( string all_err in all_errors )
  2232. 					{
  2233. 						Console.WriteLine( WordWrap( all_err, false ) );
  2234. 					}
  2235. 				}
  2236. 				else
  2237. 				{
  2238. 					Console.WriteLine( allerrors );
  2239. 				}
  2240. 				WriteLineHTML( allerrors );
  2241. 				Console.ResetColor( );
  2242. 				Console.WriteLine( );
  2243. 				rc = Math.Max( 1, rc );
  2244. 				if ( logging )
  2245. 				{
  2246. 					StreamWriter log = new StreamWriter( logfile, !replacelog );
  2247. 					log.WriteLine( "Time          :  {0}", DateTime.Now.ToString( "yyyy-MM-dd HH:mm:ss" ) );
  2248. 					log.WriteLine( "Program       :  BatCodeCheck, Version {0}", progver );
  2249. 					log.WriteLine( "Arguments     :  {0}", allargs );
  2250. 					log.WriteLine( "File name     :  \"{0}\"", Path.GetFullPath( source ) );
  2251. 					log.WriteLine( "File date     :  {0}", File.GetLastWriteTime( source ).ToString( "yyyy-MM-dd HH:mm:ss" ) );
  2252. 					log.WriteLine( "File encoding :  {0}", encoding );
  2253. 					log.WriteLine( "Tests         :  {0}", checks );
  2254. 					foreach ( char check in checks )
  2255. 					{
  2256. 						string test = check.ToString( ).ToUpper( );
  2257. 						log.WriteLine( "                 {0} =  {1}", test, tests[test] );
  2258. 					}
  2259. 					if ( envvarerrors.Count > 0 )
  2260. 					{
  2261. 						log.WriteLine( "Local environment variables are {0}", ( includelocalvars ? "included" : "ignored" ) );
  2262. 					}
  2263. 					log.WriteLine( "{0}{1}{1}", allerrors, newline );
  2264. 					log.Close( );
  2265. 				}
  2266. 			}
  2267. 		}
  2268.  
  2269.  
  2270. 		public static string EscapedHTML( char chr )
  2271. 		{
  2272. 			switch ( chr )
  2273. 			{
  2274. 				case '&':
  2275. 					return "&amp;";
  2276. 				case '>':
  2277. 					return "&gt;";
  2278. 				case '<':
  2279. 					return "&lt;";
  2280. 				default:
  2281. 					return chr.ToString( );
  2282. 			}
  2283. 		}
  2284.  
  2285.  
  2286. 		public static string GetEncoding( string filename )
  2287. 		{
  2288. 			// Based on code by Jason Pierce (2Toad) on StackOverflow.com
  2289. 			// https://stackoverflow.com/a/19283954
  2290. 			// Determines a text file's encoding by analyzing its byte order mark (BOM).
  2291. 			// Defaults to ASCII when detection of the text file's endianness fails.
  2292.  
  2293. 			// Read the BOM
  2294. 			var bom = new byte[4];
  2295. 			using ( var file = new FileStream( filename, FileMode.Open, FileAccess.Read ) )
  2296. 			{
  2297. 				file.Read( bom, 0, 4 );
  2298. 			}
  2299.  
  2300. 			// Analyze the BOM
  2301. 			if ( bom[0] == 0x2b && bom[1] == 0x2f && bom[2] == 0x76 )
  2302. 				return "UTF-7";
  2303. 			if ( bom[0] == 0xef && bom[1] == 0xbb && bom[2] == 0xbf )
  2304. 				return "UTF-8";
  2305. 			if ( bom[0] == 0xff && bom[1] == 0xfe && bom[2] == 0 && bom[3] == 0 )
  2306. 				return "UTF-32LE";
  2307. 			if ( bom[0] == 0xff && bom[1] == 0xfe )
  2308. 				return "UTF-16LE";
  2309. 			if ( bom[0] == 0xfe && bom[1] == 0xff )
  2310. 				return "UTF-16BE";
  2311. 			if ( bom[0] == 0 && bom[1] == 0 && bom[2] == 0xfe && bom[3] == 0xff )
  2312. 				return "UTF-32BE";
  2313.  
  2314. 			// We actually have no idea what the encoding is if we reach this point, so
  2315. 			// you may wish to return null instead of defaulting to ASCII
  2316. 			return "ASCII";
  2317. 		}
  2318.  
  2319.  
  2320. 		public static List<string> ListInternalCommands( )
  2321. 		{
  2322. 			string comspec = Environment.GetEnvironmentVariable( "COMSPEC" );
  2323. 			StreamReader file = new StreamReader( comspec, Encoding.ASCII );
  2324. 			string content = file.ReadToEnd( );
  2325. 			file.Close( );
  2326.  
  2327. 			List<string> intcmds = new List<string>( );
  2328. 			string excludelist = "ABOVENORMAL,AFFINITY,ANSI,APPICON,ASCII,AZ,BAT,BELOWNORMAL,BOTH,CMD,CMDCMDLINE,CMDEXTVERSION,COM,COMSPEC,CONFIG,COPYCMD,COPYRIGHT,CRLF,CSVFS,CTRL,CURRENT,DEFINED,DIRCMD,DISABLEDELAYEDEXPANSION,DISABLEEXTENSIONS,DLL,DO,DOC,DOS,DWORD,ENABLEDELAYEDEXPANSION,ENABLEEXTENSIONS,ELSE,ENTER,EOF,EQU,ERROR,ERRORLEVEL,EXE,EXIST,EXISTS,EXPAND,FALSE,FAT,FH,GEQ,GTR,GUI,HIGH,HIGHESTNUMANODENUMBER,HH,HKEY,HSM,IDI,IDLE,IN,INFO,IS,JS,KERNEL,LEQ,LIST,LNK,LOCAL,LOW,LSS,MACHINE,MAX,MIN,MM,MSC,MUI,NEQ,NODE,NORMAL,NOT,NT,NTDLL,NTFS,NY,NYA,OFF,ON,OTHER,PATHEXT,PROCESSING,RANDOM,REALTIME,REFS,REG,REGEDT,SCRIPT,SEPARATE,SHARED,STACK,SYS,SZ,TEMP,TWO,UNC,UNCC,UNKNOWN,US,USER,VAR,VBS,VERSION,VS,WAIT,WA,WC,WD,WINDOWS,WKERNEL,WORD,WP,WS,WV,XCOPY,XP";
  2329. 			List<string> exclude = excludelist.Split( ",".ToCharArray( ) ).ToList<string>( );
  2330.  
  2331. 			string pattern = @"([A-Z]\0){2,}";
  2332. 			Regex regex = new Regex( pattern );
  2333. 			if ( regex.IsMatch( content ) )
  2334. 			{
  2335. 				foreach ( Match match in regex.Matches( content ) )
  2336. 				{
  2337. 					string intcmd = match.ToString( ).Replace( "\0", string.Empty );
  2338. 					if ( !exclude.Contains( intcmd ) && !intcmds.Contains( intcmd ) )
  2339. 					{
  2340. 						intcmds.Add( intcmd );
  2341. 					}
  2342. 				}
  2343. 				intcmds.Sort( );
  2344. 			}
  2345. 			if ( intcmds.Count == 0 )
  2346. 			{
  2347. 				// Return a default list if we could not find the internal commands in %COMSPEC%
  2348. 				string defaultinternalcommands = @"ASSOC|BREAK|CALL|CD|CHDIR|CLS|COLOR|COPY|DATE|DEL|DIR|DPATH|ECHO|ENDLOCAL|ERASE|EXIT|FOR|FTYPE|GOTO|IF|KEYS|MD|MKDIR|MKLINK|MOVE|PATH|PAUSE|POPD|PROMPT|PUSHD|RD|REM|REN|RENAME|RMDIR|SET|SETLOCAL|SHIFT|START|TIME|TITLE|TYPE|VER|VERIFY|VOL";
  2349. 				intcmds = defaultinternalcommands.Split( "|".ToCharArray( ), StringSplitOptions.RemoveEmptyEntries ).ToList<string>( );
  2350. 			}
  2351. 			return intcmds;
  2352. 		}
  2353.  
  2354.  
  2355. 		public static int OpenHTMLFile( )
  2356. 		{
  2357. 			if ( openhtml )
  2358. 			{
  2359. 				try
  2360. 				{
  2361. 					if ( File.Exists( htmlfile ) )
  2362. 					{
  2363. 						Process.Start( htmlfile );
  2364. 					}
  2365. 					else
  2366. 					{
  2367. 						return ShowHelp( string.Format( "Cannot find HTML log file \"{0}\"", htmlfile ) );
  2368. 					}
  2369. 				}
  2370. 				catch ( Exception e )
  2371. 				{
  2372. 					return ShowHelp( e.Message );
  2373. 				}
  2374. 			}
  2375. 			return 0;
  2376. 		}
  2377.  
  2378.  
  2379. 		public static int OpenLogFile( )
  2380. 		{
  2381. 			if ( openlog )
  2382. 			{
  2383. 				try
  2384. 				{
  2385. 					if ( File.Exists( logfile ) )
  2386. 					{
  2387. 						Process.Start( logfile );
  2388. 					}
  2389. 					else
  2390. 					{
  2391. 						return ShowHelp( string.Format( "Cannot find ASCII log file \"{0}\"", logfile ) );
  2392. 					}
  2393. 				}
  2394. 				catch ( Exception e )
  2395. 				{
  2396. 					return ShowHelp( e.Message );
  2397. 				}
  2398. 			}
  2399. 			return 0;
  2400. 		}
  2401.  
  2402.  
  2403. 		public static void ParseLine( string line )
  2404. 		{
  2405. 			string nocomments = StripComments( line );
  2406. 			ResetInlineVariables( );
  2407. 			DisplayLineNumber( linenum );
  2408. 			CheckCommonMistakes( nocomments );
  2409. 			CheckUndefinedVariables( nocomments );
  2410. 			CheckCommandLineArguments( nocomments );
  2411. 			CheckBadPractices( nocomments );
  2412. 			CheckBestPractices( nocomments );
  2413. 			ParseLineAndHighlight( line );
  2414. 			CheckVulnerabilities( nocomments );
  2415. 			DisplayMessagesPerLine( line );
  2416. 		}
  2417.  
  2418.  
  2419. 		public static void ParseLineAndHighlight( string line )
  2420. 		{
  2421. 			// Comments are highlighted and not interpreted at all.
  2422. 			// This may spell trouble in case of multiple commands on a single line.
  2423. 			if ( new Regex( @"^\s*(REM($|\s)|::)", ignorecase ).IsMatch( line ) )
  2424. 			{
  2425. 				string match = new Regex( @"^(\s*(REM($|\s)|::))", ignorecase ).Match( line ).ToString( );
  2426. 				Console.ForegroundColor = intcmdfgcolor;
  2427. 				Console.Write( match );
  2428. 				WriteSpanHTML( match );
  2429. 				Console.ForegroundColor = commentfgscolor;
  2430. 				line = line.Substring( match.Length );
  2431. 				Console.WriteLine( line );
  2432. 				WriteSpanHTML( line );
  2433. 				Console.ResetColor( );
  2434. 			}
  2435. 			// do not highlight ECHO ON or ECHO OFF as ECHOed text
  2436. 			else if ( new Regex( @"^\s*@?ECHO\s+(ON|OFF)\s*$", ignorecase ).IsMatch( line ) )
  2437. 			{
  2438. 				Console.ResetColor( );
  2439. 				Console.WriteLine( line );
  2440. 				WriteSpanHTML( line );
  2441. 			}
  2442. 			// check if delayed variable expansion is enabled (will choke on nested SETLOCALs)
  2443. 			else if ( new Regex( @"^\s*SETLOCAL\s+((EN|DIS)ABLEEXTENSIONS\s+)?ENABLEDELAYEDEXPANSION(\b|$)", ignorecase ).IsMatch( line ) )
  2444. 			{
  2445. 				delayedvarexp = true;
  2446. 				delayedvarglobal = true;
  2447. 				Console.ResetColor( );
  2448. 				Console.WriteLine( line );
  2449. 				WriteSpanHTML( line );
  2450. 			}
  2451. 			// check if delayed variable expansion is disabled (will choke on nested SETLOCALs)
  2452. 			else if ( new Regex( @"^\s*ENDLOCAL\b", ignorecase ).IsMatch( line ) )
  2453. 			{
  2454. 				delayedvarexp = false;
  2455. 				Console.ResetColor( );
  2456. 				Console.WriteLine( line );
  2457. 				WriteSpanHTML( line );
  2458. 			}
  2459. 			else
  2460. 			{
  2461. 				// ParseLinePerCharacter( line );
  2462. 				ParseLinePerCharacter2( line );
  2463. 			}
  2464. 		}
  2465.  
  2466.  
  2467. 		public static void ParseLinePerCharacter( string line )
  2468. 		{
  2469. 			#region Initialize Inline Variables
  2470.  
  2471. 			string part = string.Empty;
  2472. 			string buildword = string.Empty;
  2473. 			string lastword = string.Empty;
  2474. 			int doublequotes = 0;
  2475. 			int singlequotes = 0;
  2476. 			bool doublepercent = false;
  2477. 			bool doublequoted = false;
  2478. 			bool echoed = false;
  2479. 			bool escnext = false;
  2480. 			bool forfloop = false;
  2481. 			bool inexcl = false;
  2482. 			bool inperc = false;
  2483. 			bool isparenthesis = false;
  2484. 			bool redirection = false;
  2485. 			bool singlequoted = false;
  2486. 			bool tempdblquoted = false;
  2487. 			bool tempinexcl = false;
  2488. 			bool tempinperc = false;
  2489. 			bool tempsngquoted = false;
  2490. 			bool tildestr = false;
  2491.  
  2492. 			#endregion Initialize Inline Variables
  2493.  
  2494. 			// check if inside a FOR /F loop
  2495. 			forfloop = new Regex( @"(^\s*|\b|\(|\&)FOR\s+/F\s", ignorecase ).IsMatch( line );
  2496.  
  2497. 			char prevchr = ' ';
  2498. 			int index = -1;
  2499.  
  2500. 			foreach ( char chr in line )
  2501. 			{
  2502. 				index += 1;
  2503.  
  2504. 				#region Character Type
  2505.  
  2506. 				switch ( chr )
  2507. 				{
  2508. 					case '"':
  2509. 						doublequoted = ( doublequoted ^ !escnext ) && !echoed;
  2510. 						if ( !escnext && !echoed )
  2511. 						{
  2512. 							doublequotes += 1;
  2513. 						}
  2514. 						doublepercent = false;
  2515. 						escnext = false;
  2516. 						inexcl = false;
  2517. 						inperc = false;
  2518. 						redirection = false;
  2519. 						tildestr = false;
  2520. 						break;
  2521. 					case (char) 39:
  2522. 						singlequoted = ( singlequoted ^ !escnext ) && !echoed;
  2523. 						if ( !escnext && !echoed && !doublequoted )
  2524. 						{
  2525. 							singlequotes += 1;
  2526. 						}
  2527. 						doublepercent = false;
  2528. 						escnext = false;
  2529. 						inexcl = false;
  2530. 						inperc = false;
  2531. 						redirection = false;
  2532. 						tildestr = false;
  2533. 						break;
  2534. 					case '^':
  2535. 						doublepercent = false;
  2536. 						escnext = ( prevchr != chr );
  2537. 						inexcl = false;
  2538. 						inperc = false;
  2539. 						tildestr = false;
  2540. 						break;
  2541. 					case '%':
  2542. 						if ( prevchr == '%' )
  2543. 						{
  2544. 							doublepercent = true;
  2545. 						}
  2546. 						else
  2547. 						{
  2548. 							doublepercent = false;
  2549. 							inperc = !inperc;
  2550. 						}
  2551. 						escnext = false;
  2552. 						inexcl = false;
  2553. 						redirection = false;
  2554. 						tildestr = false;
  2555. 						break;
  2556. 					case '!':
  2557. 						if ( delayedvarexp )
  2558. 						{
  2559. 							inexcl = !inexcl;
  2560. 						}
  2561. 						doublepercent = false;
  2562. 						escnext = false;
  2563. 						inperc = false;
  2564. 						redirection = false;
  2565. 						tildestr = false;
  2566. 						break;
  2567. 					case '~':
  2568. 						if ( prevchr == '%' )
  2569. 						{
  2570. 							inperc = false;
  2571. 							tildestr = true;
  2572. 						}
  2573. 						else if ( prevchr == '!' )
  2574. 						{
  2575. 							inexcl = false;
  2576. 							tildestr = true;
  2577. 						}
  2578. 						escnext = false;
  2579. 						redirection = false;
  2580. 						break;
  2581. 					case '&':
  2582. 						if ( prevchr == chr )
  2583. 						{
  2584. 							doublequoted = false;
  2585. 							echoed = false;
  2586. 							inperc = false;
  2587. 							redirection = false;
  2588. 						}
  2589. 						else
  2590. 						{
  2591. 							redirection = !escnext;
  2592. 						}
  2593. 						if ( singlequoted && redirection )
  2594. 						{
  2595. 							message = "Unescaped ampersand(s) in singlequoted text (FOR /F loop" + ( forfloop ? string.Empty : "?" ) + ")";
  2596. 							if ( errormsg.IndexOf( message ) > -1 )
  2597. 							{
  2598. 								allerrors += string.Format( "{0}Line {1,5:0}:\t{2}", newline, linenum, message );
  2599. 								errormsg += string.Format( "{0}{1}.", newline, message );
  2600. 							}
  2601. 							errinline = true;
  2602. 						}
  2603. 						doublepercent = false;
  2604. 						escnext = false;
  2605. 						inexcl = false;
  2606. 						inperc = false;
  2607. 						tildestr = false;
  2608. 						break;
  2609. 					case '|':
  2610. 						if ( prevchr == chr )
  2611. 						{
  2612. 							doublequoted = false;
  2613. 							echoed = false;
  2614. 							inperc = false;
  2615. 							redirection = false;
  2616. 						}
  2617. 						else
  2618. 						{
  2619. 							redirection = !escnext;
  2620. 						}
  2621. 						if ( singlequoted && redirection )
  2622. 						{
  2623. 							message = "Unescaped pipe symbol(s) in singlequoted text (FOR /F loop" + ( forfloop ? string.Empty : "?" ) + ")";
  2624. 							if ( errormsg.IndexOf( message ) > -1 )
  2625. 							{
  2626. 								allerrors += string.Format( "{0}Line {1,5:0}:\t{2}", newline, linenum, message );
  2627. 								errormsg += string.Format( "{0}{1}.", newline, message );
  2628. 							}
  2629. 							errinline = true;
  2630. 						}
  2631. 						doublepercent = false;
  2632. 						escnext = false;
  2633. 						echoed = false;
  2634. 						incomment = false;
  2635. 						inexcl = false;
  2636. 						inperc = false;
  2637. 						tildestr = false;
  2638. 						break;
  2639. 					case '>':
  2640. 						redirection = !escnext;
  2641. 						if ( singlequoted && redirection )
  2642. 						{
  2643. 							message = "Unescaped \"greater than\" symbol(s) in singlequoted text (FOR /F loop" + ( forfloop ? string.Empty : "?" ) + ")";
  2644. 							if ( errormsg.IndexOf( message ) > -1 )
  2645. 							{
  2646. 								allerrors += string.Format( "{0}Line {1,5:0}:\t{2}", newline, linenum, message );
  2647. 								errormsg += string.Format( "{0}{1}.", newline, message );
  2648. 							}
  2649. 							errinline = true;
  2650. 						}
  2651. 						doublepercent = false;
  2652. 						escnext = false;
  2653. 						echoed = false;
  2654. 						incomment = false;
  2655. 						inexcl = false;
  2656. 						inperc = false;
  2657. 						tildestr = false;
  2658. 						break;
  2659. 					case '<':
  2660. 						redirection = !escnext;
  2661. 						if ( singlequoted && redirection )
  2662. 						{
  2663. 							message = "Unescaped \"less than\" symbol(s) in singlequoted text (FOR /F loop" + ( forfloop ? string.Empty : "?" ) + ")";
  2664. 							if ( errormsg.IndexOf( message ) > -1 )
  2665. 							{
  2666. 								allerrors += string.Format( "{0}Line {1,5:0}:\t{2}", newline, linenum, message );
  2667. 								errormsg += string.Format( "{0}{1}.", newline, message );
  2668. 							}
  2669. 							errinline = true;
  2670. 						}
  2671. 						doublepercent = false;
  2672. 						escnext = false;
  2673. 						inexcl = false;
  2674. 						inperc = false;
  2675. 						tildestr = false;
  2676. 						break;
  2677. 					case ' ':
  2678. 					case '\t':
  2679. 						if ( inexcl )
  2680. 						{
  2681. 							unterminatedexclamations += 1;
  2682. 							message = "Unterminated exclamation marks or whitespace in (delayed) variable name";
  2683. 							if ( errormsg.IndexOf( message ) > -1 )
  2684. 							{
  2685. 								allerrors += string.Format( "{0}Line {1,5:0}:\t{2}", newline, linenum, message );
  2686. 								errormsg += string.Format( "{0}{1}.", newline, message );
  2687. 							}
  2688. 							errinline = true;
  2689. 						}
  2690. 						if ( inperc )
  2691. 						{
  2692. 							unterminatedexclamations += 1;
  2693. 							message = "Unterminated percent signs or whitespace in variable name";
  2694. 							if ( errormsg.IndexOf( message ) > -1 )
  2695. 							{
  2696. 								allerrors += string.Format( "{0}Line {1,5:0}:\t{2}", newline, linenum, message );
  2697. 								errormsg += string.Format( "{0}{1}.", newline, message );
  2698. 							}
  2699. 							errinline = true;
  2700. 						}
  2701. 						doublepercent = false;
  2702. 						escnext = false;
  2703. 						inexcl = false;
  2704. 						inperc = false;
  2705. 						redirection = false;
  2706. 						tildestr = false;
  2707. 						break;
  2708. 					case '(':
  2709. 						if ( !echoed && !escnext )
  2710. 						{
  2711. 							parenthesis += 1;
  2712. 						}
  2713. 						doublepercent = false;
  2714. 						//inexcl = false;
  2715. 						//inperc = false;
  2716. 						isparenthesis = true;
  2717. 						redirection = false;
  2718. 						tildestr = false;
  2719. 						break;
  2720. 					case ')':
  2721. 						if ( !echoed && !escnext )
  2722. 						{
  2723. 							parenthesis -= 1;
  2724. 						}
  2725. 						if ( parenthesis < 0 )
  2726. 						{
  2727. 							message = "Too many closing parenthesis";
  2728. 							if ( allerrors.IndexOf( message ) == -1 )
  2729. 							{
  2730. 								allerrors += string.Format( "{0}Line {1,5:0}:\t{2}", newline, linenum, message );
  2731. 								errormsg += string.Format( "{0}{1}.", newline, message );
  2732. 								errinline = true;
  2733. 							}
  2734. 						}
  2735. 						doublepercent = false;
  2736. 						//inexcl = false;
  2737. 						//inperc = false;
  2738. 						isparenthesis = true;
  2739. 						redirection = false;
  2740. 						tildestr = false;
  2741. 						break;
  2742. 					case 'a':
  2743. 					case 'd':
  2744. 					case 'f':
  2745. 					case 'n':
  2746. 					case 'p':
  2747. 					case 's':
  2748. 					case 't':
  2749. 					case 'x':
  2750. 					case 'z':
  2751. 						// "$PATH:" is ignored in the highlighting
  2752. 						if ( tildestr )
  2753. 						{
  2754. 							inexcl = false;
  2755. 							inperc = false;
  2756. 							tildestr = true;
  2757. 						}
  2758. 						escnext = false;
  2759. 						redirection = false;
  2760. 						break;
  2761. 					case '0':
  2762. 					case '1':
  2763. 					case '2':
  2764. 					case '3':
  2765. 					case '4':
  2766. 					case '5':
  2767. 					case '6':
  2768. 					case '7':
  2769. 					case '8':
  2770. 					case '9':
  2771. 						if ( ( prevchr == '%' ) || tildestr )
  2772. 						{
  2773. 							inexcl = false;
  2774. 							inperc = false;
  2775. 							tildestr = true;
  2776. 						}
  2777. 						escnext = false;
  2778. 						redirection = false;
  2779. 						break;
  2780. 					default:
  2781. 						doublepercent = false;
  2782. 						escnext = false;
  2783. 						redirection = false;
  2784. 						tildestr = false;
  2785. 						if ( tildestr )
  2786. 						{
  2787. 							if ( "ABCDEFGHIJKLMNOPQRSTUVWXYZ?".IndexOf( chr.ToString( ), StringComparison.InvariantCultureIgnoreCase ) > -1 )
  2788. 							{
  2789. 								if ( "adfnpstxz".IndexOf( prevchr.ToString( ) ) > -1 )
  2790. 								{
  2791. 									if ( line.IndexOf( "%%" + chr.ToString( ) ) > 3 && line.IndexOf( "%%" + chr.ToString( ) ) < index )
  2792. 									{
  2793. 										tildestr = true;
  2794. 									}
  2795. 								}
  2796. 							}
  2797. 						}
  2798. 						break;
  2799. 				}
  2800.  
  2801. 				#endregion Character Type
  2802.  
  2803. 				// check for unescaped redirection symbols in FOR /F loops
  2804. 				if ( redirection && singlequoted && ( parenthesis % 2 == 1 ) )
  2805. 				{
  2806. 					message = "Unescaped redirection symbol(s) in single quoted text (FOR /F loop?)";
  2807. 					if ( errormsg.IndexOf( message ) > -1 )
  2808. 					{
  2809. 						allerrors += string.Format( "{0}Line {1,5:0}:\t{2}", newline, linenum, message );
  2810. 						errormsg += string.Format( "{0}{1}.", newline, message );
  2811. 					}
  2812. 					errinline = true;
  2813. 				}
  2814.  
  2815. 				#region ECHOed text and comments
  2816.  
  2817. 				// check if we are dealing with ECHOed text or comments
  2818. 				if ( chr == ' ' || chr == '\t' )
  2819. 				{
  2820. 					lastword = buildword.ToUpper( ).Trim( );
  2821. 					buildword = string.Empty;
  2822. 				}
  2823. 				else
  2824. 				{
  2825. 					buildword += chr.ToString( );
  2826. 				}
  2827. 				if ( lastword == "ECHO" || lastword == "@ECHO" )
  2828. 				{
  2829. 					echoed = true;
  2830. 				}
  2831. 				else if ( ( lastword == "REM" || lastword == "::" ) && !echoed )
  2832. 				{
  2833. 					incomment = true;
  2834. 					commentlines += 1;
  2835. 				}
  2836.  
  2837. 				#endregion ECHOed text and comments
  2838.  
  2839. 				// as soon as an unescaped redirection symbol is encountered, we no longer have ECHOed text
  2840. 				if ( redirection && !doublequoted && !escnext )
  2841. 				{
  2842. 					echoed = false;
  2843. 				}
  2844.  
  2845. 				#region Highlighting
  2846.  
  2847. 				// correctly highlight the character we just parsed
  2848. 				if ( redirection )
  2849. 				{
  2850. 					// redirection symbols and parenthesis are highlighted in red
  2851. 					Console.ForegroundColor = redirectionfgcolor;
  2852. 				}
  2853. 				else if ( inexcl || inperc || tildestr || doublepercent || tempinexcl || tempinperc )
  2854. 				{
  2855. 					// variables and parameters are highlighted in green
  2856. 					Console.ForegroundColor = variablesfgcolor;
  2857. 				}
  2858. 				else if ( isparenthesis && !echoed )
  2859. 				{
  2860. 					// redirection symbols and parenthesis are highlighted in red
  2861. 					Console.ForegroundColor = parenthesisfgcolor;
  2862. 				}
  2863. 				else if ( echoed )
  2864. 				{
  2865. 					// ECHOed text is highlighted in cyan, with the exception of variables
  2866. 					Console.ForegroundColor = echoedtextfgcolor;
  2867. 				}
  2868. 				else if ( ( ( prevchr == (char) 39 ) && ( chr == ')' ) ) || singlequoted || tempsngquoted )
  2869. 				{
  2870. 					// singlequoted text in parenthesis is highlighted in dark gray
  2871. 					Console.ForegroundColor = singlequotedfgcolor;
  2872. 				}
  2873. 				else if ( doublequoted || tempdblquoted )
  2874. 				{
  2875. 					Console.ForegroundColor = doublequotedfgcolor;
  2876. 				}
  2877. 				else
  2878. 				{
  2879. 					Console.ResetColor( );
  2880. 				}
  2881. 				Console.Write( chr );
  2882. 				WriteSpanHTML( EscapedHTML( chr ) );
  2883.  
  2884. 				#endregion Highlighting
  2885.  
  2886. 				switch ( chr )
  2887. 				{
  2888. 					case '"':
  2889. 						tempdblquoted = doublequoted;
  2890. 						redirection = false;
  2891. 						tildestr = false;
  2892. 						break;
  2893. 					case (char) 39:
  2894. 						tempsngquoted = singlequoted;
  2895. 						redirection = false;
  2896. 						tildestr = false;
  2897. 						break;
  2898. 					case '%':
  2899. 						tempinperc = inperc;
  2900. 						redirection = false;
  2901. 						break;
  2902. 					case '!':
  2903. 						if ( prevchr != '^' )
  2904. 						{
  2905. 							tempinexcl = inexcl;
  2906. 							redirection = false;
  2907. 						}
  2908. 						break;
  2909. 					case '^':
  2910. 						if ( prevchr == chr )
  2911. 						{
  2912. 							prevchr = ' ';
  2913. 						}
  2914. 						escnext = false;
  2915. 						tildestr = false;
  2916. 						break;
  2917. 					case '&':
  2918. 					case '|':
  2919. 					case '>':
  2920. 					case '<':
  2921. 						redirection = true;
  2922. 						escnext = false;
  2923. 						tildestr = false;
  2924. 						break;
  2925. 					case '0':
  2926. 					case '1':
  2927. 					case '2':
  2928. 					case '3':
  2929. 					case '4':
  2930. 					case '5':
  2931. 					case '6':
  2932. 					case '7':
  2933. 					case '8':
  2934. 					case '9':
  2935. 						if ( inperc && tildestr )
  2936. 						{
  2937. 							inperc = false;
  2938. 						}
  2939. 						tildestr = false;
  2940. 						break;
  2941. 					case ' ':
  2942. 					case '\t':
  2943. 					case '.':
  2944. 						tildestr = false;
  2945. 						break;
  2946. 					default:
  2947. 						redirection = false;
  2948. 						tildestr = false;
  2949. 						break;
  2950. 				}
  2951. 				isparenthesis = false;
  2952. 				prevchr = chr;
  2953. 			}
  2954. 			Console.WriteLine( );
  2955. 			if ( doublequotes % 2 == 1 )
  2956. 			{
  2957. 				unterminateddoublequotes += 1;
  2958. 				message = "Unterminated doublequotes";
  2959. 				allwarnings += string.Format( "{0}Line {1,5:0}:\t{2}", newline, linenum, message );
  2960. 				warnmsg += string.Format( "{0}{1}.", newline, message );
  2961. 				errinline = true;
  2962. 			}
  2963. 			if ( singlequotes % 2 == 1 )
  2964. 			{
  2965. 				unterminatedsinglequotes += 1;
  2966. 				message = "Unterminated singlequotes";
  2967. 				allwarnings += string.Format( "{0}Line {1,5:0}:\t{2}", newline, linenum, message );
  2968. 				warnmsg += string.Format( "{0}{1}.", newline, message );
  2969. 				errinline = true;
  2970. 			}
  2971. 			if ( inperc && !tildestr )
  2972. 			{
  2973. 				unterminatedpercentsigns += 1;
  2974. 				message = "Unterminated percent signs";
  2975. 				allwarnings += string.Format( "{0}Line {1,5:0}:\t{2}", newline, linenum, message );
  2976. 				warnmsg += string.Format( "{0}{1}.", newline, message );
  2977. 				errinline = true;
  2978. 			}
  2979. 			if ( inexcl && delayedvarexp )
  2980. 			{
  2981. 				int exclmt = new Regex( @"!" ).Matches( line ).Count;
  2982. 				int escexcl = new Regex( @"\^\^!" ).Matches( line ).Count; // Exclude escaped exclamation marks
  2983. 				if ( ( exclmt - escexcl ) % 2 == 1 )
  2984. 				{
  2985. 					unterminatedexclamations += 1;
  2986. 					message = "Unterminated exclamation marks";
  2987. 					allwarnings += string.Format( "{0}Line {1,5:0}:\t{2}", newline, linenum, message );
  2988. 					warnmsg += string.Format( "{0}{1}.", newline, message );
  2989. 					errinline = true;
  2990. 				}
  2991. 			}
  2992. 		}
  2993.  
  2994.  
  2995. 		public static void ParseLinePerCharacter2( string line )
  2996. 		{
  2997. 			string pattern;
  2998. 			Regex regex;
  2999.  
  3000. 			// First, check if the line starts with an internal command
  3001. 			pattern = string.Format( @"(^\s*)((?:{0})(?=$|\s|\b)|ECHO(?=[\s/\\[\]\(\.,;:]))", intcmdpattern );
  3002. 			regex = new Regex( pattern, ignorecase );
  3003. 			if ( regex.IsMatch( line ) )
  3004. 			{
  3005. 				MatchCollection matches = regex.Matches( line );
  3006. 				foreach ( Match match in matches )
  3007. 				{
  3008. 					string whitespace = match.Groups[1].ToString( );
  3009. 					string intcmd = match.Groups[2].ToString( );
  3010. 					Console.BackgroundColor = intcmdbgcolor;
  3011. 					Console.ForegroundColor = intcmdfgcolor;
  3012. 					//intcmd = string.Format( "{0}{1}", whitespace, intcmd );
  3013. 					Console.Write( whitespace + intcmd );
  3014. 					WriteSpanHTML( whitespace + intcmd );
  3015. 					Console.ResetColor( );
  3016. 					line = line.Substring( Math.Min( whitespace.Length + intcmd.Length, line.Length - 1 ) );
  3017. 				}
  3018. 			}
  3019. 			ParseLinePerCharacter( line );
  3020. 		}
  3021.  
  3022.  
  3023. 		public static void ReadSource( string file )
  3024. 		{
  3025. 			StreamReader src = new StreamReader( file );
  3026. 			// Read batch source as pure ASCII, which is an absolute requirement for batch files
  3027. 			allsrc = Encoding.ASCII.GetString( Encoding.ASCII.GetBytes( src.ReadToEnd( ) ) );
  3028. 			src.Close( );
  3029. 			Regex rxcmnt = new Regex( @"(?:^|\n|\r|\(|&)\s*((?:REM|::)[^\n\r]+)", ignorecase );
  3030. 			allsrc_nocomments = rxcmnt.Replace( allsrc, string.Empty );
  3031. 		}
  3032.  
  3033.  
  3034. 		public static void ResetInlineVariables( )
  3035. 		{
  3036. 			badprmsg = string.Empty;
  3037. 			errinline = false;
  3038. 			errormsg = string.Empty;
  3039. 			tipsmsg = string.Empty;
  3040. 			warnmsg = string.Empty;
  3041. 			message = string.Empty;
  3042. 			part = string.Empty;
  3043. 			buildword = string.Empty;
  3044. 			lastword = string.Empty;
  3045. 		}
  3046.  
  3047.  
  3048. 		public static void RestoreConsoleColors( )
  3049. 		{
  3050. 			Console.ForegroundColor = windowfgcolor;
  3051. 			Console.BackgroundColor = windowbgcolor;
  3052. 		}
  3053.  
  3054.  
  3055. 		public static int ShowHelp( params string[] errorMessage )
  3056. 		{
  3057. 			// Delete HTML log file in case of errors
  3058. 			if ( htmlopen )
  3059. 			{
  3060. 				htmlstream.Close( );
  3061. 				File.Delete( htmlfile );
  3062. 			}
  3063.  
  3064. 			Console.ResetColor( );
  3065.  
  3066.  
  3067. 			#region Error Message
  3068.  
  3069. 			if ( errorMessage.Length > 0 )
  3070. 			{
  3071. 				List<string> errargs = new List<string>( errorMessage );
  3072. 				errargs.RemoveAt( 0 );
  3073. 				Console.Error.WriteLine( );
  3074. 				Console.ForegroundColor = ConsoleColor.Red;
  3075. 				Console.Error.Write( "ERROR:\t" );
  3076. 				Console.ForegroundColor = ConsoleColor.White;
  3077. 				Console.Error.WriteLine( errorMessage[0], errargs.ToArray( ) );
  3078. 				Console.ResetColor( );
  3079. 			}
  3080.  
  3081. 			#endregion Error Message
  3082.  
  3083.  
  3084. 			#region Help Text
  3085.  
  3086. 			/*
  3087. 			BatCodeCheck,  Version 0.42 beta
  3088. 			Search batch source code for common errors
  3089.  
  3090. 			Usage:   BATCODECHECK  batchfile [ logging ] [ options ]
  3091.  
  3092. 			Where:   batchfile       is the batch file to be checked and highlighted
  3093.  
  3094. 			Logging: /L[:"logfile"]  Log results in ASCII format, replace file if it exists
  3095. 			         /H[:"htmlfile"] log results in HTML format, replace file if it exists
  3096. 			                         (default output file names and paths equal batchfile
  3097. 			                         name and path, with extension .log or .html)
  3098.  
  3099. 			Options: /C:checks       run only the specified Checks, where "checks" is a
  3100. 			                         combination of the following letters:
  3101. 			                         A    command line Arguments for batch commands
  3102. 			                         B    Best practice tips
  3103. 			                         E    Environment variables
  3104. 			                         L    Labels
  3105. 			                         M    common Mistakes
  3106. 			                         S    verbose Summary (variables, labels, subroutines)
  3107. 			                         U    Undefined environment variables
  3108. 			                         V    Vulnerabilities
  3109. 			         /D              Debug mode: append error stack trace to error messages
  3110. 			         /E              include local Environment in defined variables list
  3111. 			         /O              Open log file(s) when done (requires /L and/or /H)
  3112. 			         /S              Split one-liners into separate command lines
  3113. 			         /T              add a Timestamp to the default output file names
  3114. 			         /U              check for BatCodeCheck Updates
  3115. 			         /W              Wrap messages in source code (default: chop them)
  3116.  
  3117. 			Notes:   The source code is displayed with highlighted ECHOed text,
  3118. 			         comments, quoted strings, redirection, parenthesis and variables.
  3119. 			         A warning message will be displayed if code errors ** were found.
  3120. 			         Warning messages in the source code are chopped by default, and
  3121. 			         repeated again (wrapped) at the end of the source code.
  3122. 			         A non-zero return code means something was wrong, either on
  3123. 			         the command line or in the source code. A return code greater
  3124. 			         than 1 indicates the last line where an error ** was detected.
  3125. 			         If no errors are detected, nothing will be logged.
  3126. 			         ** This program is based on regular expressions, it does not interpret
  3127. 			         the source code; hence false positives cannot be prevented entirely.
  3128. 			         My motto when writing this program was "I'd rather have 10 false
  3129. 			         positives than 1 missed error".
  3130. 			         So, wherever errors are mentioned, keep in mind that these may not be
  3131. 			         errors at all, just some code that needs to be investigated because
  3132. 			         it triggered one of the regex-based rules.
  3133. 			         Use common sense when evaluating the warnings and error messages.
  3134.  
  3135. 			         -- THIS PROGRAM IS NO LONGER SUPPORTED --
  3136.  
  3137. 			Written by Rob van der Woude
  3138. 			https://www.robvanderwoude.com
  3139. 			*/
  3140.  
  3141. 			#endregion Help Text
  3142.  
  3143.  
  3144. 			#region Display Help
  3145.  
  3146. 			Console.Error.WriteLine( );
  3147.  
  3148. 			Console.Error.WriteLine( "BatCodeCheck,  Version {0} beta", progver );
  3149.  
  3150. 			Console.Error.WriteLine( "Search batch source code for common errors" );
  3151.  
  3152. 			Console.Error.WriteLine( );
  3153.  
  3154. 			Console.Error.Write( "Usage:   " );
  3155. 			Console.ForegroundColor = ConsoleColor.White;
  3156. 			Console.Error.WriteLine( "BATCODECHECK  batchfile [ logging ] [ options ]" );
  3157. 			Console.ResetColor( );
  3158.  
  3159. 			Console.Error.WriteLine( );
  3160.  
  3161. 			Console.Error.Write( "Where:   " );
  3162. 			Console.ForegroundColor = ConsoleColor.White;
  3163. 			Console.Error.Write( "batchfile" );
  3164. 			Console.ResetColor( );
  3165. 			Console.Error.Write( "       is the " );
  3166. 			Console.ForegroundColor = ConsoleColor.White;
  3167. 			Console.Error.Write( "batch file" );
  3168. 			Console.ResetColor( );
  3169. 			Console.Error.WriteLine( " to be checked and highlighted" );
  3170.  
  3171. 			Console.Error.WriteLine( );
  3172.  
  3173. 			Console.Error.Write( "Logging: " );
  3174. 			Console.ForegroundColor = ConsoleColor.White;
  3175. 			Console.Error.Write( "/L[:\"logfile\"]  L" );
  3176. 			Console.ResetColor( );
  3177. 			Console.Error.WriteLine( "og results in ASCII format, replace file if it exists" );
  3178.  
  3179. 			Console.ForegroundColor = ConsoleColor.White;
  3180. 			Console.Error.Write( "         /H[:\"htmlfile\"]" );
  3181. 			Console.ResetColor( );
  3182. 			Console.Error.Write( " log results in " );
  3183. 			Console.ForegroundColor = ConsoleColor.White;
  3184. 			Console.Error.Write( "H" );
  3185. 			Console.ResetColor( );
  3186. 			Console.Error.WriteLine( "TML format, replace file if it exists" );
  3187.  
  3188. 			Console.Error.Write( "                         (" );
  3189. 			Console.ForegroundColor = ConsoleColor.White;
  3190. 			Console.Error.Write( "default" );
  3191. 			Console.ResetColor( );
  3192. 			Console.Error.Write( " output file names and paths equal " );
  3193. 			Console.ForegroundColor = ConsoleColor.White;
  3194. 			Console.Error.WriteLine( "batchfile" );
  3195. 			Console.ResetColor( );
  3196.  
  3197. 			Console.Error.Write( "                         name and path, with extension " );
  3198. 			Console.ForegroundColor = ConsoleColor.White;
  3199. 			Console.Error.Write( ".log" );
  3200. 			Console.ResetColor( );
  3201. 			Console.Error.Write( " or " );
  3202. 			Console.ForegroundColor = ConsoleColor.White;
  3203. 			Console.Error.Write( ".html" );
  3204. 			Console.ResetColor( );
  3205. 			Console.Error.WriteLine( ")" );
  3206.  
  3207. 			Console.Error.WriteLine( );
  3208.  
  3209. 			Console.Error.Write( "Options: " );
  3210. 			Console.ForegroundColor = ConsoleColor.White;
  3211. 			Console.Error.Write( "/C:checks" );
  3212. 			Console.ResetColor( );
  3213. 			Console.Error.Write( "       run only the specified " );
  3214. 			Console.ForegroundColor = ConsoleColor.White;
  3215. 			Console.Error.Write( "C" );
  3216. 			Console.ResetColor( );
  3217. 			Console.Error.WriteLine( "hecks, where \"checks\" is a" );
  3218.  
  3219. 			Console.Error.WriteLine( "                         combination of the following letters:" );
  3220.  
  3221. 			Console.ForegroundColor = ConsoleColor.White;
  3222. 			Console.Error.Write( "                         A" );
  3223. 			Console.ResetColor( );
  3224. 			Console.Error.Write( "    command line " );
  3225. 			Console.ForegroundColor = ConsoleColor.White;
  3226. 			Console.Error.Write( "A" );
  3227. 			Console.ResetColor( );
  3228. 			Console.Error.WriteLine( "rguments for batch commands" );
  3229.  
  3230. 			Console.ForegroundColor = ConsoleColor.White;
  3231. 			Console.Error.Write( "                         B    B" );
  3232. 			Console.ResetColor( );
  3233. 			Console.Error.WriteLine( "est practice tips" );
  3234.  
  3235. 			Console.ForegroundColor = ConsoleColor.White;
  3236. 			Console.Error.Write( "                         E    E" );
  3237. 			Console.ResetColor( );
  3238. 			Console.Error.WriteLine( "nvironment variables" );
  3239.  
  3240. 			Console.ForegroundColor = ConsoleColor.White;
  3241. 			Console.Error.Write( "                         L    L" );
  3242. 			Console.ResetColor( );
  3243. 			Console.Error.WriteLine( "abels" );
  3244.  
  3245. 			Console.ForegroundColor = ConsoleColor.White;
  3246. 			Console.Error.Write( "                         M" );
  3247. 			Console.ResetColor( );
  3248. 			Console.Error.Write( "    common " );
  3249. 			Console.ForegroundColor = ConsoleColor.White;
  3250. 			Console.Error.Write( "M" );
  3251. 			Console.ResetColor( );
  3252. 			Console.Error.WriteLine( "istakes" );
  3253.  
  3254. 			Console.ForegroundColor = ConsoleColor.White;
  3255. 			Console.Error.Write( "                         S" );
  3256. 			Console.ResetColor( );
  3257. 			Console.Error.Write( "    verbose " );
  3258. 			Console.ForegroundColor = ConsoleColor.White;
  3259. 			Console.Error.Write( "S" );
  3260. 			Console.ResetColor( );
  3261. 			Console.Error.WriteLine( "ummary (variables, labels, subroutines)" );
  3262.  
  3263. 			Console.ForegroundColor = ConsoleColor.White;
  3264. 			Console.Error.Write( "                         U    U" );
  3265. 			Console.ResetColor( );
  3266. 			Console.Error.WriteLine( "ndefined environment variables" );
  3267.  
  3268. 			Console.ForegroundColor = ConsoleColor.White;
  3269. 			Console.Error.Write( "                         V    V" );
  3270. 			Console.ResetColor( );
  3271. 			Console.Error.WriteLine( "ulnerabilities" );
  3272.  
  3273. 			Console.ForegroundColor = ConsoleColor.White;
  3274. 			Console.Error.Write( "         /D              D" );
  3275. 			Console.ResetColor( );
  3276. 			Console.Error.WriteLine( "ebug mode: append error stack trace to error messages" );
  3277.  
  3278. 			Console.ForegroundColor = ConsoleColor.White;
  3279. 			Console.Error.Write( "         /E" );
  3280. 			Console.ResetColor( );
  3281. 			Console.Error.Write( "              include local " );
  3282. 			Console.ForegroundColor = ConsoleColor.White;
  3283. 			Console.Error.Write( "E" );
  3284. 			Console.ResetColor( );
  3285. 			Console.Error.WriteLine( "nvironment in defined variables list" );
  3286.  
  3287. 			Console.ForegroundColor = ConsoleColor.White;
  3288. 			Console.Error.Write( "         /O              O" );
  3289. 			Console.ResetColor( );
  3290. 			Console.Error.Write( "pen log file(s) when done (requires " );
  3291. 			Console.ForegroundColor = ConsoleColor.White;
  3292. 			Console.Error.Write( "/L" );
  3293. 			Console.ResetColor( );
  3294. 			Console.Error.Write( " and/or " );
  3295. 			Console.ForegroundColor = ConsoleColor.White;
  3296. 			Console.Error.Write( "/H" );
  3297. 			Console.ResetColor( );
  3298. 			Console.Error.WriteLine( ")" );
  3299.  
  3300. 			Console.ForegroundColor = ConsoleColor.White;
  3301. 			Console.Error.Write( "         /S              S" );
  3302. 			Console.ResetColor( );
  3303. 			Console.Error.WriteLine( "plit one-liners into separate command lines" );
  3304.  
  3305. 			Console.ForegroundColor = ConsoleColor.White;
  3306. 			Console.Error.Write( "         /T" );
  3307. 			Console.ResetColor( );
  3308. 			Console.Error.Write( "              add a " );
  3309. 			Console.ForegroundColor = ConsoleColor.White;
  3310. 			Console.Error.Write( "T" );
  3311. 			Console.ResetColor( );
  3312. 			Console.Error.Write( "imestamp to the " );
  3313. 			Console.ForegroundColor = ConsoleColor.White;
  3314. 			Console.Error.Write( "default" );
  3315. 			Console.ResetColor( );
  3316. 			Console.Error.WriteLine( " output file names" );
  3317.  
  3318. 			Console.ForegroundColor = ConsoleColor.White;
  3319. 			Console.Error.Write( "         /U" );
  3320. 			Console.ResetColor( );
  3321. 			Console.Error.Write( "              check for BatCodeCheck " );
  3322. 			Console.ForegroundColor = ConsoleColor.White;
  3323. 			Console.Error.Write( "U" );
  3324. 			Console.ResetColor( );
  3325. 			Console.Error.WriteLine( "pdates" );
  3326.  
  3327. 			Console.ForegroundColor = ConsoleColor.White;
  3328. 			Console.Error.Write( "         /W              W" );
  3329. 			Console.ResetColor( );
  3330. 			Console.Error.WriteLine( "rap messages in source code (default: chop them)" );
  3331.  
  3332. 			Console.Error.WriteLine( );
  3333. 			Console.Error.Write( "Notes:   The source code is displayed with highlighted " );
  3334. 			Console.ForegroundColor = echoedtextfgcolor;
  3335. 			Console.Error.Write( "ECHOed text" );
  3336. 			Console.ResetColor( );
  3337. 			Console.Error.WriteLine( "," );
  3338.  
  3339. 			Console.ForegroundColor = commentfgscolor;
  3340. 			Console.Error.Write( "         comments" );
  3341. 			Console.ResetColor( );
  3342. 			Console.Error.Write( ", " );
  3343. 			Console.ForegroundColor = doublequotedfgcolor;
  3344. 			Console.Error.Write( "quoted strings" );
  3345. 			Console.ResetColor( );
  3346. 			Console.Error.Write( ", " );
  3347. 			Console.ForegroundColor = redirectionfgcolor;
  3348. 			Console.Error.Write( "redirection" );
  3349. 			Console.ResetColor( );
  3350. 			Console.Error.Write( ", " );
  3351. 			Console.ForegroundColor = parenthesisfgcolor;
  3352. 			Console.Error.Write( "parenthesis" );
  3353. 			Console.ResetColor( );
  3354. 			Console.Error.Write( " and " );
  3355. 			Console.ForegroundColor = variablesfgcolor;
  3356. 			Console.Error.Write( "variables" );
  3357. 			Console.ResetColor( );
  3358. 			Console.Error.WriteLine( "." );
  3359.  
  3360. 			Console.Error.WriteLine( "         A warning message will be displayed if code errors ** were found." );
  3361.  
  3362. 			Console.Error.WriteLine( "         Warning messages in the source code are chopped by default, and" );
  3363.  
  3364. 			Console.Error.WriteLine( "         repeated again (wrapped) at the end of the source code." );
  3365.  
  3366. 			Console.Error.WriteLine( "         A non-zero return code means something was wrong, either on" );
  3367.  
  3368. 			Console.Error.WriteLine( "         the command line or in the source code. A return code greater" );
  3369.  
  3370. 			Console.Error.WriteLine( "         than 1 indicates the last line where an error ** was detected." );
  3371.  
  3372. 			Console.Error.WriteLine( "         ** This program is based on regular expressions, it does not interpret" );
  3373.  
  3374. 			Console.Error.WriteLine( "         the source code; hence false positives cannot be prevented entirely." );
  3375.  
  3376. 			Console.Error.WriteLine( "         My motto when writing this program was \"I'd rather have 10 false" );
  3377.  
  3378. 			Console.Error.WriteLine( "         positives than 1 missed error\"." );
  3379.  
  3380. 			Console.Error.WriteLine( "         So, wherever errors are mentioned, keep in mind that these may not be" );
  3381.  
  3382. 			Console.Error.WriteLine( "         errors at all, just some code that needs to be investigated because" );
  3383.  
  3384. 			Console.Error.WriteLine( "         it triggered one of the regex-based rules." );
  3385.  
  3386. 			Console.Error.WriteLine( "         Use common sense when evaluating the warnings and error messages." );
  3387.  
  3388. 			Console.Error.WriteLine( );
  3389.  
  3390. 			Console.ForegroundColor = ConsoleColor.Red;
  3391. 			Console.Error.WriteLine( "         -- THIS PROGRAM IS NO LONGER SUPPORTED --" );
  3392. 			Console.ResetColor( );
  3393.  
  3394. 			Console.Error.WriteLine( );
  3395.  
  3396. 			Console.Error.WriteLine( "Written by Rob van der Woude" );
  3397.  
  3398. 			Console.Error.WriteLine( "https://www.robvanderwoude.com" );
  3399.  
  3400. 			#endregion Display Help
  3401.  
  3402.  
  3403. 			return 1;
  3404. 		}
  3405.  
  3406.  
  3407. 		public static string StripComments( string line )
  3408. 		{
  3409. 			string pattern = @"^(\s*(REM\s|::))";
  3410. 			Regex regex = new Regex( pattern, RegexOptions.IgnoreCase );
  3411. 			if ( regex.IsMatch( line ) )
  3412. 			{
  3413. 				line = regex.Match( line ).Value;
  3414. 			}
  3415. 			return line;
  3416. 		}
  3417.  
  3418.  
  3419. 		public static string VerboseSummary( )
  3420. 		{
  3421. 			string verbosemessage = string.Empty;
  3422. 			if ( verbosesummary )
  3423. 			{
  3424. 				// Total number of lines
  3425. 				verbosemessage = string.Format( "{0}Checked a total of {1} lines", newline, linenum );
  3426. 				// Number of comment lines
  3427. 				verbosemessage += string.Format( "{0}{1} comment lines", newline, commentlines );
  3428. 				// Determine column width for environment variables
  3429. 				int maxlen = 0;
  3430. 				foreach ( string name in envvars.Keys )
  3431. 				{
  3432. 					maxlen = Math.Max( maxlen, name.Length );
  3433. 				}
  3434. 				// Number of environment variables and usage
  3435. 				verbosemessage += string.Format( "{0}{0}{1} environment variables were found:", newline, envvars.Count );
  3436. 				foreach ( KeyValuePair<string, int> envvar in labels )
  3437. 				{
  3438. 					verbosemessage += string.Format( "{0}{1,-" + maxlen + "}    ", newline, envvar.Key );
  3439. 					switch ( envvar.Value )
  3440. 					{
  3441. 						case 0:
  3442. 							verbosemessage += "(never referred to)";
  3443. 							break;
  3444. 						case 1:
  3445. 							verbosemessage += "(referred to once)";
  3446. 							break;
  3447. 						case 2:
  3448. 							verbosemessage += "(referred to twice)";
  3449. 							break;
  3450. 						default:
  3451. 							verbosemessage += string.Format( "(referred to {0} times)", envvar.Value );
  3452. 							break;
  3453. 					}
  3454. 				}
  3455. 				// Determine column width for labels and subroutines
  3456. 				maxlen = 0;
  3457. 				foreach ( string name in subroutines.Keys )
  3458. 				{
  3459. 					maxlen = Math.Max( maxlen, name.Length );
  3460. 				}
  3461. 				foreach ( string name in labels.Keys )
  3462. 				{
  3463. 					maxlen = Math.Max( maxlen, name.Length );
  3464. 				}
  3465. 				// Number of subroutines and calls
  3466. 				verbosemessage += string.Format( "{0}{0}{1} subroutines were found:", newline, subroutines.Count );
  3467. 				foreach ( KeyValuePair<string, int> subroutine in subroutines )
  3468. 				{
  3469. 					verbosemessage += string.Format( "{0}{1,-" + maxlen + "}    ", newline, subroutine.Key );
  3470. 					switch ( subroutine.Value )
  3471. 					{
  3472. 						case 0:
  3473. 							verbosemessage += "(never called)";
  3474. 							break;
  3475. 						case 1:
  3476. 							verbosemessage += "(called once)";
  3477. 							break;
  3478. 						case 2:
  3479. 							verbosemessage += "(called twice)";
  3480. 							break;
  3481. 						default:
  3482. 							verbosemessage += string.Format( "(called {0} times)", subroutine.Value );
  3483. 							break;
  3484. 					}
  3485. 				}
  3486. 				// Number of "normal" labels and jumps
  3487. 				verbosemessage += string.Format( "{0}{0}{1} labels were found:", newline, labels.Count - subroutines.Count );
  3488. 				foreach ( KeyValuePair<string, int> label in labels )
  3489. 				{
  3490. 					verbosemessage += string.Format( "{0}{1,-" + maxlen + "}    ", newline, label.Key );
  3491. 					switch ( label.Value )
  3492. 					{
  3493. 						case 0:
  3494. 							verbosemessage += "(never jumped to)";
  3495. 							break;
  3496. 						case 1:
  3497. 							verbosemessage += "(jumped to once)";
  3498. 							break;
  3499. 						case 2:
  3500. 							verbosemessage += "(jumped to twice)";
  3501. 							break;
  3502. 						default:
  3503. 							verbosemessage += string.Format( "(jumped to {0} times)", label.Value );
  3504. 							break;
  3505. 					}
  3506. 				}
  3507. 			}
  3508. 			return verbosemessage;
  3509. 		}
  3510.  
  3511.  
  3512. 		public static string Which( string prog )
  3513. 		{
  3514. 			bool found = false;
  3515. 			string pattern = "%~(([dpnxs]|[fs])+)0";
  3516. 			Regex regex = new Regex( pattern, ignorecase );
  3517. 			if ( regex.IsMatch( prog ) )
  3518. 			{
  3519. 				string fullpath = string.Empty;
  3520. 				string scriptpath = regex.Match( prog ).Value;
  3521. 				char[] pathcomp = regex.Match( prog.ToLower( ) ).Groups[1].Value.ToCharArray( );
  3522. 				if ( pathcomp.Contains( 'd' ) )
  3523. 				{
  3524. 					fullpath = Path.GetPathRoot( source );
  3525. 				}
  3526. 				if ( pathcomp.Contains( 'p' ) )
  3527. 				{
  3528. 					fullpath = Path.Combine( fullpath, Directory.GetParent( source ).FullName.Substring( fullpath.Length ) ) + "\\";
  3529. 				}
  3530. 				if ( pathcomp.Contains( 'n' ) )
  3531. 				{
  3532. 					fullpath = Path.Combine( fullpath, Path.GetFileNameWithoutExtension( source ) );
  3533. 				}
  3534. 				if ( pathcomp.Contains( 'x' ) )
  3535. 				{
  3536. 					fullpath = fullpath + "." + Path.GetExtension( source );
  3537. 				}
  3538. 				if ( pathcomp.Contains( 'f' ) )
  3539. 				{
  3540. 					fullpath = Path.GetFullPath( source );
  3541. 				}
  3542. 				if ( pathcomp.Contains( 's' ) )
  3543. 				{
  3544. 					fullpath = ShortFileName( fullpath );
  3545. 				}
  3546. 				prog = prog.Replace( "\"", "" );
  3547. 				prog = prog.Replace( scriptpath, fullpath );
  3548. 				if ( File.Exists( prog ) )
  3549. 				{
  3550. 					return prog;
  3551. 				}
  3552. 				else
  3553. 				{
  3554. 					return string.Empty;
  3555. 				}
  3556. 			}
  3557. 			foreach ( string folder in path )
  3558. 			{
  3559. 				if ( !found )
  3560. 				{
  3561. 					if ( !string.IsNullOrWhiteSpace( folder ) )
  3562. 					{
  3563. 						string dir = ( folder + "\\" ).Replace( "\\\\", "\\" );
  3564. 						foreach ( string ext in pathext )
  3565. 						{
  3566. 							if ( !found )
  3567. 							{
  3568. 								// The EXTERNAL program FILE to be searched MUST have an extension, either
  3569. 								// specified on the command line or one of the extensions listed in PATHEXT.
  3570. 								if ( ( prog + ext ).IndexOf( '.' ) > -1 )
  3571. 								{
  3572. 									if ( File.Exists( dir + prog + ext ) )
  3573. 									{
  3574. 										return dir + prog + ext;
  3575. 									}
  3576. 								}
  3577. 							}
  3578. 						}
  3579. 					}
  3580. 				}
  3581. 			}
  3582. 			return string.Empty;
  3583. 		}
  3584.  
  3585.  
  3586. 		public static string WordWrap( string input, bool padright = false )
  3587. 		{
  3588. 			string output = string.Empty;
  3589. 			string blankline = new string( ' ', Console.WindowWidth );
  3590. 			while ( !string.IsNullOrEmpty( input ) )
  3591. 			{
  3592. 				int brpos;
  3593. 				if ( input.Length < Console.WindowWidth )
  3594. 				{
  3595. 					if ( padright )
  3596. 					{
  3597. 						input = ( input + blankline ).Substring( 0, Console.WindowWidth - 1 );
  3598. 						brpos = Console.WindowWidth - 1;
  3599. 					}
  3600. 					else
  3601. 					{
  3602. 						brpos = input.Length;
  3603. 					}
  3604. 				}
  3605. 				else
  3606. 				{
  3607. 					brpos = input.Substring( 0, Math.Min( Console.WindowWidth - 1, input.Length ) ).LastIndexOfAny( ". -".ToCharArray( ) );
  3608. 				}
  3609. 				// Just break at window width if word length exceeds window width
  3610. 				if ( brpos == -1 )
  3611. 				{
  3612. 					brpos = Math.Min( Console.WindowWidth - 1, input.Length );
  3613. 				}
  3614. 				string msgline = input.Substring( 0, Math.Min( brpos + 1, input.Length ) ).Trim( );
  3615. 				if ( padright )
  3616. 				{
  3617. 					msgline = ( msgline + blankline ).Substring( 0, Console.WindowWidth - 1 );
  3618. 				}
  3619. 				output += msgline + newline;
  3620. 				input = input.Substring( Math.Min( brpos + 1, input.Length ) ).Trim( );
  3621. 			}
  3622. 			return output.TrimEnd( "\n\r".ToCharArray( ) );
  3623. 		}
  3624.  
  3625.  
  3626. 		public static void WriteSpanHTML( string text )
  3627. 		{
  3628. 			if ( log2html )
  3629. 			{
  3630. 				if ( !htmlopen )
  3631. 				{
  3632. 					CreateHTML( );
  3633. 				}
  3634. 				ConsoleColor newbgc = Console.BackgroundColor;
  3635. 				ConsoleColor newfgc = Console.ForegroundColor;
  3636. 				//string backgroundcolor = newbgc.ToString( ).ToLower( ).Replace( "darkyellow", "#808000" ); // darkyellow is not a valid CSS value, reported and fixed by Alfons van Zwol
  3637. 				string backgroundcolor = ConsoleColor2CSSColor( newbgc );
  3638. 				string foregroundcolor = ConsoleColor2CSSColor( newfgc );
  3639. 				string style = string.Empty;
  3640. 				if ( newbgc != lastbgc )
  3641. 				{
  3642. 					style = string.Format( "background-color: {0};", backgroundcolor );
  3643. 				}
  3644. 				if ( newfgc != lastfgc )
  3645. 				{
  3646. 					style += string.Format( " color: {0};", foregroundcolor );
  3647. 				}
  3648. 				if ( string.IsNullOrWhiteSpace( style ) )
  3649. 				{
  3650. 					htmlstream.Write( text );
  3651. 				}
  3652. 				else
  3653. 				{
  3654. 					if ( spanopen )
  3655. 					{
  3656. 						htmlstream.Write( "</span>" );
  3657. 					}
  3658. 					htmlstream.Write( "<span style=\"{0}\">{1}", style.Trim( ), text );
  3659. 					spanopen = true;
  3660. 				}
  3661. 				lastbgc = newbgc;
  3662. 				lastfgc = newfgc;
  3663. 			}
  3664. 		}
  3665.  
  3666.  
  3667. 		public static void WriteLineHTML( string text )
  3668. 		{
  3669. 			if ( log2html )
  3670. 			{
  3671. 				if ( spanopen )
  3672. 				{
  3673. 					htmlstream.Write( "</span>\n" );
  3674. 				}
  3675. 				else
  3676. 				{
  3677. 					htmlstream.Write( "\n" );
  3678. 				}
  3679. 				spanopen = false;
  3680. 				WriteSpanHTML( text );
  3681. 			}
  3682. 		}
  3683.  
  3684.  
  3685. 		#endregion Subroutines
  3686.  
  3687.  
  3688. 		#region Short Name API
  3689.  
  3690. 		// Long path to short notation by Rod Stephens
  3691. 		// http://csharphelper.com/blog/2015/01/convert-between-long-and-short-file-names-in-c/
  3692.  
  3693. 		// Define GetShortPathName API function.
  3694. 		[DllImport( "kernel32.dll", CharSet = CharSet.Auto, SetLastError = true )]
  3695. 		static extern uint GetShortPathName( string lpszLongPath, char[] lpszShortPath, int cchBuffer );
  3696.  
  3697.  
  3698. 		// Return the short file name for a long file name.
  3699. 		private static string ShortFileName( string long_name )
  3700. 		{
  3701. 			char[] name_chars = new char[1024];
  3702. 			long length = GetShortPathName( long_name, name_chars, name_chars.Length );
  3703. 			string short_name = new string( name_chars );
  3704. 			return short_name.Substring( 0, (int) length );
  3705. 		}
  3706.  
  3707. 		#endregion Short Name API
  3708. 	}
  3709. }

page last modified: 2024-04-16; loaded in 0.0613 seconds