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.41";
  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 interactive = false;
  65. 		public static bool licensed = true;
  66. 		public static bool log2html = false;
  67. 		public static bool logging = false;
  68. 		public static bool openalllogs = false;
  69. 		public static bool openlog = false;
  70. 		public static bool openhtml = false;
  71. 		public static bool replacehtml = true;
  72. 		public static bool replacelog = true;
  73. 		public static bool spanopen = false;
  74. 		public static bool splitoneliners = false;
  75. 		public static bool verbosesummary = true;
  76. 		public static bool wrap = false;
  77. 		public static string allargs;
  78. 		public static string allsrc;
  79. 		public static string allsrc_nocomments;
  80. 		public static string allerrors = string.Empty;
  81. 		public static string allbadpr = string.Empty;
  82. 		public static string alltips = string.Empty;
  83. 		public static string allwarnings = string.Empty;
  84. 		public static string allerrsum = string.Empty;
  85. 		public static string checks = "ABELMSUV";
  86. 		public static string encoding;
  87. 		public static string html = string.Empty;
  88. 		public static string htmlfile = string.Empty;
  89. 		public static string logfile = string.Empty;
  90. 		public static string newline = Environment.NewLine;
  91. 		public static string source = string.Empty;
  92. 		public static string timestamp = string.Empty;
  93. 		public static string[] path = ( Environment.CurrentDirectory + ";" + Environment.GetEnvironmentVariable( "PATH" ) ).Split( ';' );
  94. 		public static string[] pathext = ( ";" + Environment.GetEnvironmentVariable( "PATHEXT" ).ToLower( ) ).Split( ';' );
  95. 		public static Dictionary<string, string> tests;
  96. 		public static SortedList<string, int> labels = new SortedList<string, int>( );
  97. 		public static SortedList<string, int> envvars = new SortedList<string, int>( );
  98. 		public static SortedList<string, int> subroutines = new SortedList<string, int>( );
  99. 		public static List<string> upcaselabels = new List<string>( );
  100. 		public static List<string> gotoerrors = new List<string>( );
  101. 		public static List<string> callerrors = new List<string>( );
  102. 		public static List<string> upcaseenvvars = new List<string>( );
  103. 		public static List<string> envvarerrors = new List<string>( );
  104. 		public static List<string> localenvvars = new List<string>( );
  105. 		public static List<int> incorrectlinefeeds = new List<int>( );
  106. 		public static StreamWriter htmlstream;
  107. 		public static ConsoleColor badprbgcolor = ConsoleColor.Yellow;
  108. 		public static ConsoleColor badprfgcolor = ConsoleColor.Red;
  109. 		public static ConsoleColor commentfgscolor = ConsoleColor.DarkYellow;
  110. 		public static ConsoleColor doublequotedfgcolor = ConsoleColor.Yellow;
  111. 		public static ConsoleColor echoedtextfgcolor = ConsoleColor.Cyan;
  112. 		public static ConsoleColor errorbgcolor = ConsoleColor.Red;
  113. 		public static ConsoleColor errorfgcolor = ConsoleColor.White;
  114. 		public static ConsoleColor extcmdbgcolor = ConsoleColor.Black;
  115. 		public static ConsoleColor extcmdfgcolor = ConsoleColor.White;
  116. 		public static ConsoleColor intcmdbgcolor = ConsoleColor.Black;
  117. 		public static ConsoleColor intcmdfgcolor = ConsoleColor.Yellow;
  118. 		public static ConsoleColor lastbgc = Console.BackgroundColor;
  119. 		public static ConsoleColor lastfgc = Console.ForegroundColor;
  120. 		public static ConsoleColor linenumbgcolor = ConsoleColor.Gray;
  121. 		public static ConsoleColor linenumfgcolor = ConsoleColor.DarkMagenta;
  122. 		public static ConsoleColor parenthesisfgcolor = ConsoleColor.Red;
  123. 		public static ConsoleColor redirectionfgcolor = ConsoleColor.Red;
  124. 		public static ConsoleColor reportbgcolor = Console.BackgroundColor;
  125. 		public static ConsoleColor reportfgcolor = ConsoleColor.Red;
  126. 		public static ConsoleColor singlequotedfgcolor = ConsoleColor.DarkGray;
  127. 		public static ConsoleColor tipsbgcolor = ConsoleColor.Blue;
  128. 		public static ConsoleColor tipsfgcolor = ConsoleColor.White;
  129. 		public static ConsoleColor variablesfgcolor = ConsoleColor.Green;
  130. 		public static ConsoleColor warningbgcolor = ConsoleColor.DarkYellow;
  131. 		public static ConsoleColor warningfgcolor = ConsoleColor.White;
  132. 		public static ConsoleColor windowbgcolor = Console.BackgroundColor;
  133. 		public static ConsoleColor windowfgcolor = Console.ForegroundColor;
  134. 		// Inline variables
  135. 		public static string badprmsg = string.Empty;
  136. 		public static string errormsg = string.Empty;
  137. 		public static string tipsmsg = string.Empty;
  138. 		public static string warnmsg = string.Empty;
  139. 		public static string message = string.Empty;
  140. 		public static string part = string.Empty;
  141. 		public static string buildword = string.Empty;
  142. 		public static string lastword = string.Empty;
  143.  
  144.  
  145. 		#endregion Global Variables and Constants
  146.  
  147.  
  148. 		[STAThread]
  149. 		static int Main( string[] args )
  150. 		{
  151. 			// Check for updates (licensed version only)
  152. 			CheckUpdate( );
  153.  
  154.  
  155. 			#region Initialize List of Tests
  156.  
  157. 			tests = new Dictionary<string, string>( );
  158. 			tests["A"] = "command line Arguments for batch commands";
  159. 			tests["B"] = "Best practice tips";
  160. 			tests["E"] = "Environment variables";
  161. 			tests["L"] = "Labels";
  162. 			tests["M"] = "common Mistakes";
  163. 			tests["S"] = "verbose Summary (variables, labels, subroutines)";
  164. 			tests["U"] = "Undefined environment variables";
  165. 			tests["V"] = "Vulnerabilities";
  166.  
  167. 			#endregion Initialize List of Tests
  168.  
  169.  
  170. 			#region Command Line Parsing
  171.  
  172.  
  173. 			allargs = string.Join( " ", args );
  174.  
  175.  
  176. 			#region Command Line Arguments
  177.  
  178.  
  179. 			if ( args.Length == 0 || args.Length > 8 )
  180. 			{
  181. 				return ShowHelp( );
  182. 			}
  183. 			foreach ( string arg in args )
  184. 			{
  185. 				if ( arg[0] == '/' )
  186. 				{
  187. 					switch ( arg.ToUpper( ).Substring( 0, Math.Min( 2, arg.Length ) ) )
  188. 					{
  189. 						case "/?":
  190. 							return ShowHelp( );
  191. 						case "/C":
  192. 							if ( !checkall )
  193. 							{
  194. 								return ShowHelp( "Duplicate command line argument /C" );
  195. 							}
  196. 							if ( arg.Length < 4 || arg[2] != ':' )
  197. 							{
  198. 								return ShowHelp( "Invalid command line argument \"{0}\"", arg );
  199. 							}
  200. 							checks = arg.Substring( 3 ).ToUpper( );
  201. 							foreach ( char check in checks )
  202. 							{
  203. 								if ( !tests.Keys.Contains( check.ToString( ).ToUpper( ) ) )
  204. 								{
  205. 									return ShowHelp( "Invalid check \"{1}\" in command line argument \"{0}\"", arg, check.ToString( ) );
  206. 								}
  207. 							}
  208. 							checkall = false;
  209. 							checkcmdargs = ( checks.IndexOf( 'A' ) > -1 ); // command line Arguments for batch commands
  210. 							checkbestpr = ( checks.IndexOf( 'B' ) > -1 ); // Best practice tips
  211. 							checkenvvars = ( checks.IndexOf( 'E' ) > -1 ); // Environment variables
  212. 							checklabels = ( checks.IndexOf( 'L' ) > -1 ); // Labels
  213. 							checkcommerr = ( checks.IndexOf( 'M' ) > -1 ); //' common Mistakes
  214. 							verbosesummary = ( checks.IndexOf( 'S' ) > -1 ); // verbose Summary (variables, labels, subroutines)
  215. 							checkundef = ( checks.IndexOf( 'U' ) > -1 ); // Undefined environment variables
  216. 							checkvulnr = ( checks.IndexOf( 'V' ) > -1 ); // Vulnerabilities
  217. 							break;
  218. 						case "/D":
  219. 							if ( debugmode )
  220. 							{
  221. 								return ShowHelp( "Duplicate command line argument /D" );
  222. 							}
  223. 							debugmode = true;
  224. 							break;
  225. 						case "/E":
  226. 							if ( includelocalvars )
  227. 							{
  228. 								return ShowHelp( "Duplicate command line argument /E" );
  229. 							}
  230. 							includelocalvars = true;
  231. 							break;
  232. 						case "/H":
  233. 							if ( log2html )
  234. 							{
  235. 								return ShowHelp( "Duplicate command line argument /H" );
  236. 							}
  237. 							log2html = true;
  238. 							if ( arg.Length > 3 && arg.ToUpper( ).Substring( 2, 1 ) == ":" )
  239. 							{
  240. 								htmlfile = arg.Substring( 3 );
  241. 							}
  242. 							break;
  243. 						case "/I":
  244. 							if ( interactive )
  245. 							{
  246. 								return ShowHelp( "Duplicate command line argument /I" );
  247. 							}
  248. 							interactive = true;
  249. 							break;
  250. 						case "/L":
  251. 							if ( logging )
  252. 							{
  253. 								return ShowHelp( "Duplicate command line argument /L" );
  254. 							}
  255. 							logging = true;
  256. 							if ( arg.Length > 3 && arg.ToUpper( ).Substring( 2, 1 ) == ":" )
  257. 							{
  258. 								logfile = arg.Substring( 3 );
  259. 							}
  260. 							break;
  261. 						case "/O":
  262. 							if ( openalllogs )
  263. 							{
  264. 								return ShowHelp( "Duplicate command line argument /O" );
  265. 							}
  266. 							openalllogs = true;
  267. 							break;
  268. 						case "/S":
  269. 							if ( splitoneliners )
  270. 							{
  271. 								return ShowHelp( "Duplicate command line argument /S" );
  272. 							}
  273. 							splitoneliners = true;
  274. 							break;
  275. 						case "/T":
  276. 							if ( addtimestamp )
  277. 							{
  278. 								return ShowHelp( "Duplicate command line argument /T" );
  279. 							}
  280. 							addtimestamp = true;
  281. 							break;
  282. 						case "/U":
  283. 							if ( checkupdates )
  284. 							{
  285. 								return ShowHelp( "Duplicate command line argument /U" );
  286. 							}
  287. 							checkupdates = true;
  288. 							break;
  289. 						case "/W":
  290. 							if ( wrap )
  291. 							{
  292. 								return ShowHelp( "Duplicate command line argument /W" );
  293. 							}
  294. 							wrap = true;
  295. 							break;
  296. 						default:
  297. 							return ShowHelp( "Invalid command line argument \"{0}\"", arg );
  298. 					}
  299. 				}
  300. 				else
  301. 				{
  302. 					if ( string.IsNullOrEmpty( source ) )
  303. 					{
  304. 						source = arg;
  305. 					}
  306. 					else
  307. 					{
  308. 						return ShowHelp( "Invalid command line argument \"{0}\"", arg );
  309. 					}
  310. 				}
  311. 			}
  312.  
  313. 			if ( checkupdates )
  314. 			{
  315. 				string updatecheckurl = "http://www.robvanderwoude.com/getlatestver.php?progfile=BatCodeCheck.exe&amp;version=" + progver;
  316. 				Process.Start( updatecheckurl );
  317. 			}
  318.  
  319.  
  320. 			#endregion Command Line Arguments
  321.  
  322.  
  323. 			#region Validate paths
  324.  
  325.  
  326. 			if ( !interactive )
  327. 			{
  328. 				if ( string.IsNullOrEmpty( source ) )
  329. 				{
  330. 					return ShowHelp( "Please specify a source file" );
  331. 				}
  332. 				if ( !addtimestamp && source.IndexOfAny( "/?*".ToCharArray( ) ) > -1 )
  333. 				{
  334. 					return ShowHelp( );
  335. 				}
  336. 				if ( !File.Exists( source ) )
  337. 				{
  338. 					return ShowHelp( "Source file not found" );
  339. 				}
  340. 			}
  341. 			if ( !string.IsNullOrWhiteSpace( source ) )
  342. 			{
  343. 				source = Path.GetFullPath( source );
  344. 			}
  345. 			if ( !string.IsNullOrEmpty( logfile ) )
  346. 			{
  347. 				if ( !Directory.Exists( Directory.GetParent( logfile ).FullName ) )
  348. 				{
  349. 					return ShowHelp( "Invalid path to log file" );
  350. 				}
  351. 				logfile = Path.GetFullPath( logfile );
  352. 			}
  353. 			if ( !string.IsNullOrEmpty( htmlfile ) )
  354. 			{
  355. 				if ( !Directory.Exists( Directory.GetParent( htmlfile ).FullName ) )
  356. 				{
  357. 					return ShowHelp( "Invalid path to HTML file" );
  358. 				}
  359. 				htmlfile = Path.GetFullPath( htmlfile );
  360. 			}
  361.  
  362.  
  363. 			#endregion Validate paths
  364.  
  365.  
  366. 			#region BatCodeCheck Environment Variable
  367.  
  368.  
  369. 			// Local environment variable %BatCodeCheck% can be used to set defaults, but it is overruled by command line arguments
  370. 			try
  371. 			{
  372. 				string envargs = Environment.GetEnvironmentVariable( "BatCodeCheck" );
  373. 				if ( !string.IsNullOrWhiteSpace( envargs ) )
  374. 				{
  375. 					envargs = envargs.ToUpper( );
  376. 				}
  377. 				if ( new Regex( @"^\s*(/[EHILOSTW]\s*)+$" ).IsMatch( envargs ) )
  378. 				{
  379. 					foreach ( Match match in new Regex( "/[DEHILOSTW]" ).Matches( envargs ) )
  380. 					{
  381. 						switch ( match.ToString( ) )
  382. 						{
  383. 							case "/D":
  384. 								if ( !debugmode )
  385. 								{
  386. 									debugmode = true;
  387. 									allargs += " /D";
  388. 								}
  389. 								break;
  390. 							case "/E":
  391. 								if ( !includelocalvars )
  392. 								{
  393. 									includelocalvars = true;
  394. 									allargs += " /E";
  395. 								}
  396. 								break;
  397. 							case "/H":
  398. 								if ( !log2html )
  399. 								{
  400. 									log2html = true;
  401. 									allargs += " /H";
  402. 								}
  403. 								break;
  404. 							case "/I":
  405. 								if ( !interactive )
  406. 								{
  407. 									interactive = true;
  408. 									allargs += " /I";
  409. 								}
  410. 								break;
  411. 							case "/L":
  412. 								if ( !logging )
  413. 								{
  414. 									logging = true;
  415. 									allargs += " /L";
  416. 								}
  417. 								break;
  418. 							case "/O":
  419. 								if ( !openalllogs )
  420. 								{
  421. 									openalllogs = true;
  422. 									allargs += " /O";
  423. 								}
  424. 								break;
  425. 							case "/S":
  426. 								if ( !splitoneliners )
  427. 								{
  428. 									splitoneliners = true;
  429. 									allargs += " /S";
  430. 								}
  431. 								break;
  432. 							case "/T":
  433. 								if ( !addtimestamp )
  434. 								{
  435. 									addtimestamp = true;
  436. 									allargs += " /T";
  437. 								}
  438. 								break;
  439. 							case "/W":
  440. 								if ( !wrap )
  441. 								{
  442. 									wrap = true;
  443. 									allargs += " /W";
  444. 								}
  445. 								break;
  446. 						}
  447. 					}
  448. 				}
  449. 			}
  450. 			catch ( Exception )
  451. 			{
  452. 				// Environment variable BatCodeCheck not found
  453. 			}
  454.  
  455.  
  456. 			#endregion BatCodeCheck Environment Variable
  457.  
  458.  
  459. 			#region Command Line Defaults
  460.  
  461.  
  462. 			if ( !includelocalvars )
  463. 			{
  464. 				pathext = ".com;.exe;.bat;.cmd".Split( ';' );
  465. 			}
  466. 			if ( addtimestamp )
  467. 			{
  468. 				AddTimeStamp( );
  469. 			}
  470. 			if ( log2html && string.IsNullOrWhiteSpace( htmlfile ) )
  471. 			{
  472. 				htmlfile = Path.Combine( Path.GetDirectoryName( source ), Path.GetFileNameWithoutExtension( source ) + timestamp + ".html" );
  473. 			}
  474. 			if ( logging && string.IsNullOrWhiteSpace( logfile ) )
  475. 			{
  476. 				logfile = Path.Combine( Path.GetDirectoryName( source ), Path.GetFileNameWithoutExtension( source ) + timestamp + ".log" );
  477. 			}
  478.  
  479.  
  480. 			#endregion Command Line Defaults
  481.  
  482.  
  483. 			#region Command Line Dependencies
  484.  
  485.  
  486. 			openalllogs = openalllogs && ( logging || log2html );
  487. 			openhtml = openalllogs && log2html;
  488. 			openlog = openalllogs && logging;
  489.  
  490.  
  491. 			#endregion Command Line Dependencies
  492.  
  493.  
  494. 			#region Interactive
  495.  
  496.  
  497. 			if ( licensed && interactive )
  498. 			{
  499. 				try
  500. 				{
  501. 					if ( !ShowSettings( ) )
  502. 					{
  503. 						Console.Error.WriteLine( "Aborted by user request" );
  504. 						return 1;
  505. 					}
  506. 				}
  507. 				catch ( Exception )
  508. 				{
  509. 					return ShowHelp( "Interactive use of BatCodeCheck (/I switch) requires a licensed version" );
  510. 				}
  511. 			}
  512.  
  513.  
  514. 			#endregion Interactive
  515.  
  516.  
  517. 			#endregion Command Line Parsing
  518.  
  519.  
  520. #if DEBUG
  521. 			debugmode = true;
  522. #endif
  523.  
  524.  
  525. 			if ( File.Exists( source ) )
  526. 			{
  527. 				encoding = GetEncoding( source );
  528. 				ReadSource( source );
  529. 				CheckLinefeeds( );
  530. 				CollectEnvironmentVariables( );
  531. 				CollectLabels( );
  532. 				CreateHTML( );
  533.  
  534. 				// Now parse the source line by line
  535. 				rc = 0;
  536. 				foreach ( string line in allsrc.Split( new string[] { "\r\n", "\n", "\r" }, StringSplitOptions.None ) )
  537. 				{
  538. 					linenum += 1;
  539.  
  540. 					// If /S switch was used, split the line on parenthesis and ampersands, except parentheses surrounding the list in FOR loops, and except comments
  541. 					if ( splitoneliners && !new Regex( @"^\s*(::|REM)(\b|\s|$)", ignorecase ).IsMatch( line ) && line.IndexOfAny( "(&|)".ToCharArray( ) ) > -1 )
  542. 					{
  543. 						#region Split One-Liners
  544.  
  545. 						bool inelse = false;
  546. 						bool inforloop = false;
  547. 						string tmpline = string.Empty;
  548.  
  549. 						string pattern = @"(?<!\^)\((?!['\n\r])";
  550. 						Regex regex = new Regex( pattern );
  551. 						if ( regex.IsMatch( line ) )
  552. 						{
  553. 							MatchCollection matches = regex.Matches( line );
  554. 							tmpline = line.Substring( 0, matches[0].Index );
  555. 							if ( new Regex( @"\s%%[A-Z!]\s+IN\s+$", ignorecase ).IsMatch( tmpline ) )
  556. 							{
  557. 								inforloop = true;
  558. 							}
  559. 							else if ( new Regex( @"\)\s+ELSE\s+\(", ignorecase ).IsMatch( tmpline ) )
  560. 							{
  561. 								inelse = true;
  562. 							}
  563.  
  564. 						}
  565. 						if ( inforloop || inelse )
  566. 						{
  567. 							tmpline = regex.Replace( line, "(\n\t", 100, line.IndexOf( ')' ) + 1 );
  568. 						}
  569. 						else
  570. 						{
  571. 							tmpline = regex.Replace( line, "(\n\t" );
  572. 						}
  573.  
  574. 						pattern = @"(?<!['\^\n\r])\)";
  575. 						regex = new Regex( pattern );
  576. 						if ( inforloop || inelse )
  577. 						{
  578. 							tmpline = regex.Replace( tmpline, "\n)", 100, line.IndexOf( ')' ) + 1 );
  579. 						}
  580. 						else
  581. 						{
  582. 							tmpline = regex.Replace( tmpline, "\n)" );
  583. 						}
  584.  
  585. 						pattern = @"(?<!\^|2>|1>)&(?>!1|2)";
  586. 						regex = new Regex( pattern );
  587. 						tmpline = regex.Replace( tmpline, "\n\t" );
  588.  
  589. 						foreach ( string part in tmpline.Split( new string[] { "\n" }, StringSplitOptions.None ) )
  590. 						{
  591. 							if ( !string.IsNullOrWhiteSpace( part ) )
  592. 							{
  593. 								ParseLine( part );
  594. 							}
  595. 						}
  596.  
  597. 						#endregion Split One-Liners
  598. 					}
  599. 					else
  600. 					{
  601. 						ParseLine( line );
  602. 					}
  603. 				}
  604. 				DisplayMessagesSummary( );
  605. 				CloseHTML( );
  606. 				RestoreConsoleColors( );
  607. 				if ( OpenLogFile( ) + OpenHTMLFile( ) != 0 )
  608. 				{
  609. 					return 1;
  610. 				}
  611. 				return rc;
  612. 			}
  613. 			else
  614. 			{
  615. 				return ShowHelp( "File \"{0}\" not found", source );
  616. 			}
  617. 		}
  618.  
  619.  
  620. 		#region Subroutines
  621.  
  622.  
  623. 		public static void AddTimeStamp( )
  624. 		{
  625. 			if ( string.IsNullOrWhiteSpace( timestamp ) )
  626. 			{
  627. 				timestamp = DateTime.Now.ToString( ".yyyyMMddHHmmss" );
  628. 			}
  629. 		}
  630.  
  631.  
  632. 		public static void CheckBadPractices( string line )
  633. 		{
  634. 			if ( checkall || checkbadpr )
  635. 			{
  636. 				string pattern;
  637. 				Regex regex;
  638.  
  639. 				// SET dynamic variables
  640. 				#region SET dynamic variables
  641.  
  642. 				pattern = string.Format( @"\bSET\s+({0})=", dynvarpattern );
  643. 				regex = new Regex( pattern, ignorecase );
  644. 				if ( regex.IsMatch( line ) )
  645. 				{
  646. 					MatchCollection matches = regex.Matches( line );
  647. 					string dynvar = matches[0].Groups[1].Value;
  648. 					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( ) );
  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. 				#endregion SET dynamic variables
  655.  
  656. 				// SET system variables
  657. 				#region SET system variables
  658.  
  659. 				pattern = string.Format( @"\bSET\s+({0})=", sysvarpattern );
  660. 				regex = new Regex( pattern, ignorecase );
  661. 				if ( regex.IsMatch( line ) )
  662. 				{
  663. 					MatchCollection matches = regex.Matches( line );
  664. 					string dynvar = matches[0].Groups[1].Value;
  665. 					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( ) );
  666. 					allbadpr += string.Format( "{0}Line {1,5:0}:\t{2}", newline, linenum, message );
  667. 					badprmsg += string.Format( "{0}{1}.", newline, message );
  668. 					errinline = true;
  669. 				}
  670.  
  671. 				#endregion SET system variables
  672.  
  673. 				// Quoted paths
  674. 				// Disabled for now, as it is extremely hard, if possible at all, to extract a path from a command line without a true interpreter
  675. 				#region Quoted paths
  676. 				/*
  677. 			pattern = @"[^""=]((?:[A-Z]:|\\\\\w+\\\w+)(?:\\[^\\]+)*\\[^\s\\]+\s+[^\\]+(?:\\[^\\]+)*(?:\\|\b|$))";
  678. 			regex = new Regex( pattern, ignorecase );
  679. 			if ( regex.IsMatch( line ) )
  680. 			{
  681. 				MatchCollection matches = regex.Matches( line );
  682. 				string path = matches[0].Groups[1].ToString( );
  683. 				message = string.Format( "Whitespace found in unquoted path: \"{0}\"", path );
  684. 				allbadpr += string.Format( "{0}Line {1,5:0}:\t{2}", newline, linenum, message );
  685. 				badprmsg += string.Format( "{0}{1}.", newline, message );
  686. 				errinline = true;
  687. 			}
  688.  
  689. 			pattern = @"[^""]((?:[A-Z]:|\\\\\w+\\\w+)(?:\\[^\\]+)*\\[^\\]*(\(|\))[^\\]*(?:\\[^\\]+)*\\?(?:\b|$))";
  690. 			regex = new Regex( pattern, ignorecase );
  691. 			if ( regex.IsMatch( line ) )
  692. 			{
  693. 				MatchCollection matches = regex.Matches( line );
  694. 				string path = matches[0].Groups[1].ToString( );
  695. 				string spec = matches[0].Groups[2].ToString( );
  696. 				message = string.Format( "Parenthesis found in unquoted path: \"{1}\"; use doublequotes, especially inside code blocks", spec, path );
  697. 				allbadpr += string.Format( "{0}Line {1,5:0}:\t{2}", newline, linenum, message );
  698. 				badprmsg += string.Format( "{0}{1}.", newline, message );
  699. 				errinline = true;
  700. 			}
  701.  
  702. 			pattern = @"[^""=]((?:[A-Z]:|\\\\\w+\\\w+)(?:\\[^\\]+)*\\[^\\]*(&|'|,|\^)[^\\]*(?:\\[^\\]+)*\\?(?:\b|$))";
  703. 			regex = new Regex( pattern, ignorecase );
  704. 			if ( regex.IsMatch( line ) )
  705. 			{
  706. 				MatchCollection matches = regex.Matches( line );
  707. 				string path = matches[0].Groups[1].ToString( );
  708. 				string spec = matches[0].Groups[2].ToString( );
  709. 				message = string.Format( "Special character \"{0}\" found in unquoted path: \"{1}\"; use doublequotes", spec, path );
  710. 				allbadpr += string.Format( "{0}Line {1,5:0}:\t{2}", newline, linenum, message );
  711. 				badprmsg += string.Format( "{0}{1}.", newline, message );
  712. 				errinline = true;
  713. 			}
  714. 			*/
  715. 				#endregion Quoted paths
  716. 			}
  717. 		}
  718.  
  719.  
  720. 		public static void CheckBestPractices( string line )
  721. 		{
  722. 			if ( checkall || checkbestpr )
  723. 			{
  724. 				string pattern;
  725. 				Regex regex;
  726.  
  727. 				// Doublequotes in SET statements
  728. 				#region Doublequoted SET
  729.  
  730. 				pattern = @"(?:^|\b|\()(SET\s+""([\w_\.-]+)=([^""&\n\r]+)"")";
  731. 				regex = new Regex( pattern, ignorecase );
  732. 				if ( regex.IsMatch( line ) )
  733. 				{
  734. 					foreach ( Match match in regex.Matches( line ) )
  735. 					{
  736. 						string wrong = match.Groups[1].ToString( );
  737. 						string correct = string.Format( "SET {0}={1}", match.Groups[2].ToString( ), match.Groups[3].ToString( ) );
  738. 						message = string.Format( "Doublequoted SET statements are not recommended, except with /A (or /P) switch; use {0} instead of {1}", correct, wrong );
  739. 						alltips += string.Format( "{0}Line {1,5:0}:\t{2}", newline, linenum, message );
  740. 						tipsmsg += string.Format( "{0}{1}.", newline, message );
  741. 						errinline = true;
  742. 					}
  743. 				}
  744.  
  745. 				#endregion Doublequoted SET
  746.  
  747. 				// Devices with appended colons
  748. 				#region Devices with appended colons
  749.  
  750. 				pattern = @"(?:>|\b)(AUX|CLOCK\$|COM[1-8]|CON|LPT[1-8]|NUL|PRN):(?:\b|$)";
  751. 				regex = new Regex( pattern, ignorecase );
  752. 				if ( regex.IsMatch( line ) )
  753. 				{
  754. 					MatchCollection matches = regex.Matches( line );
  755. 					string device = matches[0].Groups[1].ToString( ).ToUpper( );
  756. 					message = string.Format( "Redundant colon appended to device name \"{0}\"", device );
  757. 					alltips += string.Format( "{0}Line {1,5:0}:\t{2}", newline, linenum, message );
  758. 					tipsmsg += string.Format( "{0}{1}.", newline, message );
  759. 					errinline = true;
  760. 				}
  761.  
  762. 				#endregion Devices with appended colons
  763.  
  764. 				// Obsolete commands BREAK, CTTY, KEYS, and 16-bit commands
  765. 				#region Obsolete commands
  766.  
  767. 				pattern = string.Format( @"(?:^|&|\|\()\s*({0})\b", notx32pattern );
  768. 				regex = new Regex( pattern, ignorecase );
  769. 				if ( regex.IsMatch( line ) )
  770. 				{
  771. 					MatchCollection matches = regex.Matches( line );
  772. 					foreach ( Match match in matches )
  773. 					{
  774. 						string cmd = match.Groups[1].ToString( ).ToUpper( );
  775. 						message = string.Format( "Obsolete command \"{0}\" is accepted only for MS-DOS backwards compatibility", cmd );
  776. 						alltips += string.Format( "{0}Line {1,5:0}:\t{2}", newline, linenum, message );
  777. 						tipsmsg += string.Format( "{0}{1}.", newline, message );
  778. 						obsoletecmd += 1;
  779. 					}
  780. 					errinline = true;
  781. 				}
  782.  
  783. 				pattern = string.Format( @"(?:^|&|\|\()\s*({0})\b", notx64pattern );
  784. 				regex = new Regex( pattern, ignorecase );
  785. 				if ( regex.IsMatch( line ) )
  786. 				{
  787. 					MatchCollection matches = regex.Matches( line );
  788. 					foreach ( Match match in matches )
  789. 					{
  790. 						string cmd = match.Groups[1].ToString( ).ToUpper( );
  791. 						if ( System.Environment.Is64BitOperatingSystem )
  792. 						{
  793. 							message = string.Format( "16-bit command \"{0}\" is not available in 64-bit Windows", cmd );
  794. 							allerrors += string.Format( "{0}Line {1,5:0}:\t{2}", newline, linenum, message );
  795. 							errormsg += string.Format( "{0}{1}.", newline, message );
  796. 						}
  797. 						else
  798. 						{
  799. 							message = string.Format( "16-bit command \"{0}\" will not be available in 64-bit Windows", cmd );
  800. 							alltips += string.Format( "{0}Line {1,5:0}:\t{2}", newline, linenum, message );
  801. 							tipsmsg += string.Format( "{0}{1}.", newline, message );
  802. 						}
  803. 						obsoletecmd += 1;
  804. 					}
  805. 					errinline = true;
  806. 				}
  807.  
  808. 				#endregion Obsolete commands
  809.  
  810. 				// @ all over the place
  811. 				#region Hiding command echo
  812.  
  813. 				if ( linenum > 1 )
  814. 				{
  815. 					pattern = @"^\s*@(?>!ECHO\s*(ON|OFF)\s)";
  816. 					regex = new Regex( pattern, ignorecase );
  817. 					if ( regex.IsMatch( line ) )
  818. 					{
  819. 						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.";
  820. 						alltips += string.Format( "{0}Line {1,5:0}:\t{2}", newline, linenum, message );
  821. 						tipsmsg += string.Format( "{0}{1}.", newline, message );
  822. 						errinline = true;
  823. 					}
  824. 				}
  825. 				#endregion Hiding command echo
  826. 			}
  827. 		}
  828.  
  829.  
  830. 		public static void CheckCommandLineArguments( string line )
  831. 		{
  832. 			if ( checkall || checkcmdargs )
  833. 			{
  834. 				string pattern;
  835. 				Regex regex;
  836.  
  837. 				// Invalid arguments for ASSOC command
  838. 				#region ASSOC command line
  839.  
  840. 				pattern = @"(^|\b|\()ASSOC(\b|$)";
  841. 				regex = new Regex( pattern, ignorecase );
  842. 				if ( regex.IsMatch( line ) )
  843. 				{
  844. 					pattern = @"(?:^|\b|\()ASSOC(\s+\.\w+(?:=[\w \.\-]{3,})?)?\s*$";
  845. 					regex = new Regex( pattern, ignorecase );
  846. 					if ( !regex.IsMatch( line ) )
  847. 					{
  848. 						pattern = @"(?:^|\b|\()ASSOC([^\n\r&|]+)";
  849. 						regex = new Regex( pattern, ignorecase );
  850. 						if ( regex.IsMatch( line ) )
  851. 						{
  852. 							string cla = regex.Match( line ).Groups[1].ToString( ).Trim( );
  853. 							if ( !string.IsNullOrWhiteSpace( cla ) && cla[0] != '/' ) // No need to display a message for switches, the check for switches will handle that
  854. 							{
  855. 								message = string.Format( "Invalid command line argument(s) \"{0}\" for ASSOC command", cla );
  856. 								allerrors += string.Format( "{0}Line {1,5:0}:\t{2}", newline, linenum, message );
  857. 								errormsg += string.Format( "{0}{1}.", newline, message );
  858. 								errinline = true;
  859. 							}
  860. 						}
  861. 					}
  862. 				}
  863.  
  864. 				#endregion ASSOC command line
  865.  
  866. 				// Invalid arguments for BREAK command
  867. 				#region BREAK command line
  868.  
  869. 				pattern = @"(^|\b|\()BREAK(\b|$)";
  870. 				regex = new Regex( pattern, ignorecase );
  871. 				if ( regex.IsMatch( line ) )
  872. 				{
  873. 					pattern = @"(?:^|\b|\()BREAK(\s+(ON|OFF))\s*$";
  874. 					regex = new Regex( pattern, ignorecase );
  875. 					if ( !regex.IsMatch( line ) )
  876. 					{
  877. 						pattern = @"(?:^|\b|\()BREAK([^\n\r&|]+)";
  878. 						regex = new Regex( pattern, ignorecase );
  879. 						if ( regex.IsMatch( line ) )
  880. 						{
  881. 							string cla = regex.Match( line ).Groups[1].ToString( ).Trim( );
  882. 							if ( !String.IsNullOrWhiteSpace( cla ) && cla[0] != '/' ) // No need to display a message for switches, the check for switches will handle that
  883. 							{
  884. 								message = string.Format( "Invalid command line argument \"{0}\" for BREAK command, the only arguments accepted are ON and OFF", cla );
  885. 								allerrors += string.Format( "{0}Line {1,5:0}:\t{2}", newline, linenum, message );
  886. 								errormsg += string.Format( "{0}{1}.", newline, message );
  887. 								errinline = true;
  888. 							}
  889. 						}
  890. 					}
  891. 					message = "The BREAK command is available for backward compatibility only, it does not DO anything in Windows";
  892. 					alltips += string.Format( "{0}Line {1,5:0}:\t{2}", newline, linenum, message );
  893. 					tipsmsg += string.Format( "{0}{1}.", newline, message );
  894. 					errinline = true;
  895. 				}
  896.  
  897. 				#endregion BREAK command line
  898.  
  899. 				// Invalid arguments for CALL command
  900. 				#region CALL command line
  901.  
  902. 				pattern = @"(?:^|\b|\()CALL\s+(\""?)([^\n\r\s\""]+)(\1\b|$)";
  903. 				regex = new Regex( pattern, ignorecase );
  904. 				if ( regex.IsMatch( line ) )
  905. 				{
  906. 					bool callerr = false;
  907. 					string cla = regex.Match( line ).Groups[2].ToString( );
  908. 					if ( string.IsNullOrWhiteSpace( cla ) )
  909. 					{
  910. 						callerr = true;
  911. 					}
  912. 					else
  913. 					{
  914. 						if ( cla[0] != ':' ) // CALLs to subroutine labels will be handled later
  915. 						{
  916. 							pattern = string.Format( @"^({0})$", intcmdpattern );
  917. 							regex = new Regex( pattern, ignorecase );
  918. 							if ( !regex.IsMatch( cla ) )
  919. 							{
  920. 								if ( string.IsNullOrWhiteSpace( Which( cla ) ) )
  921. 								{
  922. 									callerr = true;
  923. 								}
  924. 							}
  925. 						}
  926. 					}
  927. 					if ( callerr )
  928. 					{
  929. 						message = string.Format( "Invalid command line argument \"{0}\" for CALL command: no matching internal command, nor external command was found", cla );
  930. 						allerrors += string.Format( "{0}Line {1,5:0}:\t{2}", newline, linenum, message );
  931. 						errormsg += string.Format( "{0}{1}.", newline, message );
  932. 						errinline = true;
  933. 					}
  934. 				}
  935.  
  936. 				#endregion CALL command line
  937.  
  938. 				// Invalid switch for CD command
  939. 				#region CD switches
  940.  
  941. 				pattern = @"(?:^|\b|\()(CD|CHDIR)\s+(/[^D][^\n\r\s/]*)";
  942. 				regex = new Regex( pattern, ignorecase );
  943. 				if ( regex.IsMatch( line ) )
  944. 				{
  945. 					string cmd = regex.Match( line ).Groups[1].ToString( );
  946. 					string cls = regex.Match( line ).Groups[2].ToString( );
  947. 					message = string.Format( "Invalid command line switch \"{0}\" for {1} command, the only accepted switch is /D", cls, cmd );
  948. 					allerrors += string.Format( "{0}Line {1,5:0}:\t{2}", newline, linenum, message );
  949. 					errormsg += string.Format( "{0}{1}.", newline, message );
  950. 					errinline = true;
  951. 				}
  952.  
  953. 				#endregion CD switches
  954.  
  955. 				// Invalid argument for DATE command
  956. 				#region DATE arguments
  957.  
  958. 				pattern = @"(^|\b|\()(?<![%!])DATE(?>![%!:])(\b|$)";
  959. 				regex = new Regex( pattern, ignorecase );
  960. 				if ( regex.IsMatch( line ) )
  961. 				{
  962. 					pattern = @"(?:^|\b|\()DATE\s+(/[^T][^\^\n\r\s&|\)']*|/T[^\^\n\r\s&|\)']+)";
  963. 					regex = new Regex( pattern, ignorecase );
  964. 					if ( regex.IsMatch( line ) )
  965. 					{
  966. 						string cls = regex.Match( line ).Groups[1].ToString( );
  967. 						message = string.Format( "Invalid command line switch \"{0}\" for DATE command, the only valid switch is /T", cls );
  968. 						allerrors += string.Format( "{0}Line {1,5:0}:\t{2}", newline, linenum, message );
  969. 						errormsg += string.Format( "{0}{1}.", newline, message );
  970. 						errinline = true;
  971. 					}
  972. 					else
  973. 					{
  974. 						pattern = @"(?:^|\b|\()DATE\s+(/T|(%|!)[\w_-]{1,16}\2|[\d/-]{10})";
  975. 						regex = new Regex( pattern, ignorecase );
  976. 						if ( !regex.IsMatch( line ) )
  977. 						{
  978. 							string cls = regex.Match( line ).Groups[1].ToString( );
  979. 							message = string.Format( "Invalid command line argument \"{0}\" for DATE command, this should be a date", cls );
  980. 							allerrors += string.Format( "{0}Line {1,5:0}:\t{2}", newline, linenum, message );
  981. 							errormsg += string.Format( "{0}{1}.", newline, message );
  982. 							errinline = true;
  983. 						}
  984. 					}
  985. 				}
  986.  
  987. 				#endregion DATE arguments
  988.  
  989. 				// Invalid switch for EXIT command
  990. 				#region EXIT switches
  991.  
  992. 				pattern = @"(?:^|\b|\()EXIT\s+(/[^B][^\n\r\s/]*)";
  993. 				regex = new Regex( pattern, ignorecase );
  994. 				if ( regex.IsMatch( line ) )
  995. 				{
  996. 					string cls = regex.Match( line ).Groups[1].ToString( );
  997. 					message = string.Format( "Invalid command line switch \"{0}\" for EXIT command, the only accepted switch is /B", cls );
  998. 					allerrors += string.Format( "{0}Line {1,5:0}:\t{2}", newline, linenum, message );
  999. 					errormsg += string.Format( "{0}{1}.", newline, message );
  1000. 					errinline = true;
  1001. 				}
  1002.  
  1003. 				#endregion EXIT switches
  1004.  
  1005. 				// Invalid switch for FOR command
  1006. 				#region FOR switches
  1007.  
  1008. 				pattern = @"(?:^|\b|\()FOR\s+(/[^DFLR][^\n\r\s=/]*)";
  1009. 				regex = new Regex( pattern, ignorecase );
  1010. 				if ( regex.IsMatch( line ) )
  1011. 				{
  1012. 					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( ) );
  1013. 					allerrors += string.Format( "{0}Line {1,5:0}:\t{2}", newline, linenum, message );
  1014. 					errormsg += string.Format( "{0}{1}.", newline, message );
  1015. 					errinline = true;
  1016. 				}
  1017.  
  1018. 				#endregion FOR switches
  1019.  
  1020. 				// Invalid arguments for FTYPE command
  1021. 				#region FTYPE command line
  1022.  
  1023. 				pattern = @"(^|\b|\()FTYPE(\b|$)";
  1024. 				regex = new Regex( pattern, ignorecase );
  1025. 				if ( regex.IsMatch( line ) )
  1026. 				{
  1027. 					pattern = @"(?:^|\b|\()FTYPE(\s+[^\.\n\r&|][\w \.\-]{2,}(=[\w: \.\\\-\(\)]+)?)?\s*$";
  1028. 					regex = new Regex( pattern, ignorecase );
  1029. 					if ( !regex.IsMatch( line ) )
  1030. 					{
  1031. 						pattern = @"(?:^|\b|\()FTYPE\s+([^\n\r&|]+)";
  1032. 						regex = new Regex( pattern, ignorecase );
  1033. 						if ( regex.IsMatch( line ) )
  1034. 						{
  1035. 							string cla = regex.Match( line ).Groups[1].ToString( ).Trim( );
  1036. 							if ( !String.IsNullOrWhiteSpace( cla ) && cla[0] != '/' ) // No need to display a message for switches, the check for switches will handle that
  1037. 							{
  1038. 								message = string.Format( "Invalid command line argument(s) \"{0}\" for FTYPE command", cla );
  1039. 								allerrors += string.Format( "{0}Line {1,5:0}:\t{2}", newline, linenum, message );
  1040. 								errormsg += string.Format( "{0}{1}.", newline, message );
  1041. 								errinline = true;
  1042. 							}
  1043. 						}
  1044. 					}
  1045. 				}
  1046.  
  1047. 				#endregion FTYPE command line
  1048.  
  1049. 				// Invalid switch for IF command
  1050. 				#region IF switches
  1051.  
  1052. 				pattern = @"(?:^|\b|\()(?<!/)IF\s+(/[^I][^\n\r\s/]*)";
  1053. 				regex = new Regex( pattern, ignorecase );
  1054. 				if ( regex.IsMatch( line ) )
  1055. 				{
  1056. 					string cls = regex.Match( line ).Groups[1].ToString( );
  1057. 					message = string.Format( "Invalid command line switch \"{0}\" for IF command, the only accepted switch is /I", cls );
  1058. 					allerrors += string.Format( "{0}Line {1,5:0}:\t{2}", newline, linenum, message );
  1059. 					errormsg += string.Format( "{0}{1}.", newline, message );
  1060. 					errinline = true;
  1061. 				}
  1062.  
  1063. 				#endregion IF switches
  1064.  
  1065. 				// Invalid switch for SET command
  1066. 				#region SET switches
  1067.  
  1068. 				pattern = @"(?:^|\b|\()SET\s+(/[^AP][^\n\r\s=/]*)";
  1069. 				regex = new Regex( pattern, ignorecase );
  1070. 				if ( regex.IsMatch( line ) )
  1071. 				{
  1072. 					message = string.Format( "Invalid command line switch \"{0}\" for SET command, accepted switches are /A and /P", regex.Match( line ).Groups[1].ToString( ) );
  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. 				#endregion SET switches
  1079.  
  1080. 				// Invalid switch for SHIFT command
  1081. 				#region SHIFT switches
  1082.  
  1083. 				pattern = @"(?:^|\b|\()SHIFT\s+(/[^0-8][^\n\r\s/]*)";
  1084. 				regex = new Regex( pattern, ignorecase );
  1085. 				if ( regex.IsMatch( line ) )
  1086. 				{
  1087. 					string cls = regex.Match( line ).Groups[1].ToString( );
  1088. 					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 );
  1089. 					allerrors += string.Format( "{0}Line {1,5:0}:\t{2}", newline, linenum, message );
  1090. 					errormsg += string.Format( "{0}{1}.", newline, message );
  1091. 					errinline = true;
  1092. 				}
  1093.  
  1094. 				#endregion SHIFT switches
  1095.  
  1096. 				// Invalid argument for TIME command
  1097. 				#region TIME arguments
  1098.  
  1099. 				pattern = @"(^|\b|\()(?<![%!])TIME(?>![%!:])(\b|$)";
  1100. 				regex = new Regex( pattern, ignorecase );
  1101. 				if ( regex.IsMatch( line ) )
  1102. 				{
  1103. 					pattern = @"(?:^|\b|\()TIME\s+(/[^T][^\^\n\r\s&|\)']*|/T[^\^\n\r\s&|\)']+)";
  1104. 					regex = new Regex( pattern, ignorecase );
  1105. 					if ( regex.IsMatch( line ) )
  1106. 					{
  1107. 						string cls = regex.Match( line ).Groups[1].ToString( );
  1108. 						message = string.Format( "Invalid command line switch \"{0}\" for TIME command, the only valid switch is /T", cls );
  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. 					else
  1114. 					{
  1115. 						pattern = @"(?:^|\b|\()TIME\s+(/T|(%|!)[\w_-]{1,16}\2|[\d:\.,AMP\s]{4,11})";
  1116. 						regex = new Regex( pattern, ignorecase );
  1117. 						if ( !regex.IsMatch( line ) )
  1118. 						{
  1119. 							string cls = regex.Match( line ).Groups[1].ToString( );
  1120. 							message = string.Format( "Invalid command line argument \"{0}\" for TIME command, this should be a time value", cls );
  1121. 							allerrors += string.Format( "{0}Line {1,5:0}:\t{2}", newline, linenum, message );
  1122. 							errormsg += string.Format( "{0}{1}.", newline, message );
  1123. 							errinline = true;
  1124. 						}
  1125. 					}
  1126. 				}
  1127.  
  1128. 				#endregion TIME arguments
  1129.  
  1130. 				// Switches not allowed for ASSOC, CALL, COLOR, FTYPE, GOTO, MD, MKDIR, PUSHD, SETLOCAL
  1131. 				#region Switches not allowed
  1132.  
  1133. 				pattern = @"(?:^|\b|\()(ASSOC|CALL|COLOR|FTYPE|GOTO|MD|MKDIR|PUSHD|SETLOCAL)\s+(/[^\n\r\s=/]*)";
  1134. 				regex = new Regex( pattern, ignorecase );
  1135. 				if ( regex.IsMatch( line ) )
  1136. 				{
  1137. 					string cmd = regex.Match( line ).Groups[1].ToString( ).ToUpper( );
  1138. 					string cls = regex.Match( line ).Groups[2].ToString( );
  1139. 					message = string.Format( "Switch \"{1}\" for {0} command is not valid, as {0} does not accept switches at all", cmd, cls );
  1140. 					allerrors += string.Format( "{0}Line {1,5:0}:\t{2}", newline, linenum, message );
  1141. 					errormsg += string.Format( "{0}{1}.", newline, message );
  1142. 					errinline = true;
  1143. 				}
  1144.  
  1145. 				#endregion Switches not allowed
  1146.  
  1147. 				// Arguments not required for CLS, ENDLOCAL, PAUSE, POPD, VER
  1148. 				#region Arguments not required
  1149.  
  1150. 				pattern = @"(?:^|\b|\()(CLS|ENDLOCAL|PAUSE|POPD|VER)\s+([^\n\r&|>]+)";
  1151. 				regex = new Regex( pattern, ignorecase );
  1152. 				if ( regex.IsMatch( line ) )
  1153. 				{
  1154. 					string cmd = regex.Match( line ).Groups[1].ToString( ).ToUpper( );
  1155. 					string cla = regex.Match( line ).Groups[2].ToString( );
  1156. 					message = string.Format( "Argument \"{1}\" for {0} command is not valid, as {0} does not accept arguments at all", cmd, cla );
  1157. 					allerrors += string.Format( "{0}Line {1,5:0}:\t{2}", newline, linenum, message );
  1158. 					errormsg += string.Format( "{0}{1}.", newline, message );
  1159. 					errinline = true;
  1160. 				}
  1161.  
  1162. 				#endregion Arguments not required
  1163. 			}
  1164. 		}
  1165.  
  1166.  
  1167. 		public static void CheckCommonMistakes( string line )
  1168. 		{
  1169. 			if ( checkall || checkcommerr )
  1170. 			{
  1171. 				string pattern;
  1172. 				Regex regex;
  1173.  
  1174. 				// IF [NOT] EXISTS or IF [NOT] EXIT or IF [NOT] EXITS instead of IF EXIST
  1175. 				#region IF EXIST
  1176.  
  1177. 				pattern = @"\bIF(\s+NOT)?\s+EXI(STS|T|TS)\s";
  1178. 				regex = new Regex( pattern, ignorecase );
  1179. 				if ( regex.IsMatch( line ) )
  1180. 				{
  1181. 					//message = "Found \"IF EXISTS ...\" (note the incorrect trailing \"S\"); use \"IF EXIST ...\" (without trailing \"S\") instead";
  1182. 					MatchCollection matches = regex.Matches( line );
  1183. 					string matchall = matches[0].ToString( ).ToUpper( );
  1184. 					string matchnot = matches[0].Groups[1].ToString( ).ToUpper( );
  1185. 					string matchend = matches[0].Groups[2].ToString( ).ToUpper( );
  1186. 					switch ( matchend )
  1187. 					{
  1188. 						case "STS":
  1189. 							message = string.Format( "Found \"{0} ...\" (note the incorrect trailing \"S\"); use \"IF{1} EXIST ...\" (without trailing \"S\") instead", matchall, matchnot );
  1190. 							break;
  1191. 						case "T":
  1192. 							message = string.Format( "Found \"{0} ...\" (note the missing \"S\"); use \"IF{1} EXIST ...\" (with \"S\") instead", matchall, matchnot );
  1193. 							break;
  1194. 						case "TS":
  1195. 							message = string.Format( "Found \"{0} ...\" (note the swapped \"S\" and \"T\"); use \"IF{1} EXIST ...\" (\"S\" first, followed by \"T\") instead", matchall, matchnot );
  1196. 							break;
  1197. 					}
  1198. 					allerrors += string.Format( "{0}Line {1,5:0}:\t{2}", newline, linenum, message );
  1199. 					errormsg += string.Format( "{0}{1}.", newline, message );
  1200. 					errinline = true;
  1201. 				}
  1202.  
  1203. 				#endregion IF EXIST
  1204.  
  1205. 				// Common typos for %AllUsersProfile% variable
  1206. 				#region AllUsersProfile
  1207.  
  1208. 				pattern = @"(%|!)(A+L+USERPROFILES?)(\1)";
  1209. 				regex = new Regex( pattern, ignorecase );
  1210. 				if ( regex.IsMatch( line ) )
  1211. 				{
  1212. 					MatchCollection matches = regex.Matches( line );
  1213. 					foreach ( Match match in matches )
  1214. 					{
  1215. 						string wrong = match.Groups[2].ToString( ).ToUpper( );
  1216. 						string varchr = match.Groups[1].ToString( );
  1217. 						message = string.Format( "Possible typo: the system variable name is spelled \"{0}ALLUSERSPROFILE{0}\", not \"{0}{1}{0}\"", varchr, wrong );
  1218. 						allwarnings += string.Format( "{0}Line {1,5:0}:\t{2}", newline, linenum, message );
  1219. 						warnmsg += string.Format( "{0}{1}.", newline, message );
  1220. 						errinline = true;
  1221. 					}
  1222. 				}
  1223.  
  1224. 				#endregion AllUsersProfile
  1225.  
  1226. 				// IF [NOT] ERRORLEVEL 0
  1227. 				#region IF ERRORLEVEL 0
  1228.  
  1229. 				pattern = @"(^|\b|\()IF\s+ERRORLEVEL\s+0\s";
  1230. 				regex = new Regex( pattern, ignorecase );
  1231. 				if ( regex.IsMatch( line ) )
  1232. 				{
  1233. 					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";
  1234. 					allbadpr += string.Format( "{0}Line {1,5:0}:\t{2}", newline, linenum, message );
  1235. 					badprmsg += string.Format( "{0}{1}.", newline, message );
  1236. 					errinline = true;
  1237. 					rc = linenum;
  1238. 				}
  1239.  
  1240. 				pattern = @"(^|\b|\()IF\s+NOT\s+ERRORLEVEL\s+0\s";
  1241. 				regex = new Regex( pattern, ignorecase );
  1242. 				if ( regex.IsMatch( line ) )
  1243. 				{
  1244. 					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";
  1245. 					allbadpr += string.Format( "{0}Line {1,5:0}:\t{2}", newline, linenum, message );
  1246. 					badprmsg += string.Format( "{0}{1}.", newline, message );
  1247. 					errinline = true;
  1248. 					rc = linenum;
  1249. 				}
  1250.  
  1251. 				#endregion IF ERRORLEVEL 0
  1252.  
  1253. 				// :: inside code blocks
  1254. 				#region Double colons in code blocks
  1255.  
  1256. 				// check if :: is used inside a code block, but skip REM or ECHO followed by ::
  1257. 				bool codeblock = ( parenthesis > 0 );
  1258. 				bool dblcolcmnt = ( line.IndexOf( "::" ) > line.IndexOfAny( "(&|".ToCharArray( ) ) );
  1259. 				bool echoedcmnt = new Regex( @"^([^:]*|([^:]*:[^:]+)*)ECHO\b[^\n\r&\|\>]*::", ignorecase ).IsMatch( line ); // Skip if ECHO is found before :: and no redirection in between
  1260. 				bool remcomment = new Regex( @"^([^:]*|([^:]*:[^:]+)*)REM\b[^\n\r&]*::", ignorecase ).IsMatch( line ); // Skip if REM is found before :: and no ampersand in between
  1261. 				if ( dblcolcmnt && codeblock && !remcomment && !echoedcmnt )
  1262. 				{
  1263. 					message = "Found \"::\" inside a code block (i.e. in parenthesis); use \"REM\" instead";
  1264. 					allerrors += string.Format( "{0}Line {1,5:0}:\t{2}", newline, linenum, message );
  1265. 					errormsg += string.Format( "{0}{1}.", newline, message );
  1266. 					errinline = true;
  1267. 					rc = linenum;
  1268. 				}
  1269.  
  1270. 				#endregion Double colons in code blocks
  1271.  
  1272. 				// CALLs to undefined subroutines
  1273. 				#region CALLs
  1274.  
  1275. 				if ( new Regex( @"(?:^|\s)CALL\s+:", ignorecase ).IsMatch( line ) )
  1276. 				// if ( new Regex( @"(?:^|\b)CALL\s+:", ignorecase ).IsMatch( line ) )
  1277. 				{
  1278. 					//MatchCollection matches = new Regex( @"(?:^|\b)CALL\s+:([\w\.\[\]\/+\-\(\)\{\}]{1,127})(?=$|\s|\)|&|\|)", ignorecase ).Matches( line );
  1279. 					MatchCollection matches = new Regex( @"(?:^|\s)CALL\s+:([\w\.\[\]\/+\-\(\)\{\}]{1,127})(?=$|\s|\)|&|\|)", ignorecase ).Matches( line );
  1280. 					foreach ( Match match in matches )
  1281. 					{
  1282. 						string label = match.Groups[1].ToString( );
  1283. 						if ( !labels.Keys.Contains( label ) )
  1284. 						{
  1285. 							callerrors.Add( label );
  1286. 							message = string.Format( "A CALL is made to a subroutine named \"{0}\" but the subroutine was not found", label );
  1287. 							allerrors += string.Format( "{0}Line {1,5:0}:\t{2}", newline, linenum, message );
  1288. 							errormsg += string.Format( "{0}{1}.", newline, message );
  1289. 							errinline = true;
  1290. 						}
  1291. 					}
  1292. 				}
  1293.  
  1294. 				#endregion CALLs
  1295.  
  1296. 				// GOTOs to undefined labels
  1297. 				#region GOTOs
  1298.  
  1299. 				if ( new Regex( @"(?:^|\b|\s|\()GOTO\s", ignorecase ).IsMatch( line ) )
  1300. 				{
  1301. 					MatchCollection matches = new Regex( @"(?:^|\b|\s|\()GOTO\s+:?([\w\.\[\]/+\-]{1,127})[\)&|\s]*", ignorecase ).Matches( line );
  1302. 					foreach ( Match match in matches )
  1303. 					{
  1304. 						string label = match.Groups[1].ToString( );
  1305. 						if ( label.ToUpper( ) != "EOF" && !labels.Keys.Contains( label ) )
  1306. 						{
  1307. 							gotoerrors.Add( label );
  1308. 							message = string.Format( "A jump (GOTO) to a label named \"{0}\" was detected, but the label was not found", label );
  1309. 							allerrors += string.Format( "{0}Line {1,5:0}:\t{2}", newline, linenum, message );
  1310. 							errormsg += string.Format( "{0}{1}.", newline, message );
  1311. 							errinline = true;
  1312. 						}
  1313. 					}
  1314. 				}
  1315.  
  1316. 				#endregion GOTOs
  1317.  
  1318. 				// Redirected Standard Error typos
  1319. 				#region Redirected Standard Error
  1320.  
  1321. 				pattern = @"\s(2(?:>>|<+|\s+)&\s*1)(?:\b|$)";
  1322. 				regex = new Regex( pattern, ignorecase );
  1323. 				if ( regex.IsMatch( line ) )
  1324. 				{
  1325. 					MatchCollection matches = regex.Matches( line );
  1326. 					string redirect = matches[0].Groups[1].ToString( ).ToUpper( );
  1327. 					message = string.Format( "Incorrect redirection of Standard Error to Standard Output: \"{0}\"", redirect );
  1328. 					allerrors += string.Format( "{0}Line {1,5:0}:\t{2}", newline, linenum, message );
  1329. 					errormsg += string.Format( "{0}{1}.", newline, message );
  1330. 					errinline = true;
  1331. 				}
  1332.  
  1333. 				pattern = @"\s(1(?:>>|<+|\s+)&\s*2)(?:\b|$)";
  1334. 				regex = new Regex( pattern, ignorecase );
  1335. 				if ( regex.IsMatch( line ) )
  1336. 				{
  1337. 					MatchCollection matches = regex.Matches( line );
  1338. 					string redirect = matches[0].Groups[1].ToString( ).ToUpper( );
  1339. 					message = string.Format( "Incorrect redirection of Standard Output to Standard Error: \"{0}\"", redirect );
  1340. 					allerrors += string.Format( "{0}Line {1,5:0}:\t{2}", newline, linenum, message );
  1341. 					errormsg += string.Format( "{0}{1}.", newline, message );
  1342. 					errinline = true;
  1343. 				}
  1344.  
  1345. 				pattern = @"2>&1[ \t""]*[^$&\|\)]";
  1346. 				regex = new Regex( pattern, ignorecase );
  1347. 				if ( regex.IsMatch( line ) )
  1348. 				{
  1349. 					message = "Redirection of Standard Error to Standard Output (2>&1) should always be placed at the end of the command";
  1350. 					allbadpr += string.Format( "{0}Line {1,5:0}:\t{2}", newline, linenum, message );
  1351. 					badprmsg += string.Format( "{0}{1}.", newline, message );
  1352. 					errinline = true;
  1353. 				}
  1354.  
  1355. 				#endregion Redirected Standard Error
  1356.  
  1357. 				// Unescaped redirection in FOR /F loops
  1358. 				#region Unescaped redirection in FOR /F loops
  1359.  
  1360. 				pattern = @"FOR\s+/F\s+(""((delims|eol|skip|tokens)=[^ ""]*\s*|usebackq\s*)*"")?\s*%%[A-Z?]\s+IN\s+\('[^\^']*[\&\<\>\|]";
  1361. 				regex = new Regex( pattern, ignorecase );
  1362. 				if ( regex.IsMatch( line ) )
  1363. 				{
  1364. 					message = "Unescaped redirection symbol(s) in a FOR /F loop";
  1365. 					allerrors += string.Format( "{0}Line {1,5:0}:\t{2}", newline, linenum, message );
  1366. 					errormsg += string.Format( "{0}{1}.", newline, message );
  1367. 					errinline = true;
  1368. 				}
  1369.  
  1370. 				#endregion Unescaped redirection in FOR /F loops
  1371.  
  1372. 				// Unescaped ECHOed parenthesis inside code block
  1373. 				#region Unescaped ECHOed parenthesis inside code block
  1374.  
  1375. 				if ( parenthesis > 0 ) // if we're inside a code block...
  1376. 				{
  1377. 					pattern = @"(^|\b)ECHO(?=\.|\s)[^&\|\n\r\(\)^]*[^^][\(\)]";
  1378. 				}
  1379. 				else
  1380. 				{
  1381. 					pattern = @"\(\s*ECHO(?=\.|\s)[^&\|\n\r\(\)^]*[^^][\(\)]";
  1382. 				}
  1383. 				regex = new Regex( pattern, ignorecase );
  1384. 				if ( regex.IsMatch( line ) )
  1385. 				{
  1386. 					message = "Unescaped ECHOed parenthesis inside a code block";
  1387. 					allerrors += string.Format( "{0}Line {1,5:0}:\t{2}", newline, linenum, message );
  1388. 					errormsg += string.Format( "{0}{1}.", newline, message );
  1389. 					errinline = true;
  1390. 				}
  1391.  
  1392. 				#endregion Unescaped ECHOed parenthesis inside code block
  1393.  
  1394. 				// Delayed variables with delayed variable expansion disabled vv.
  1395. 				#region Delayed variables without delayed expansion
  1396.  
  1397. 				if ( delayedvarexp )
  1398. 				{
  1399. 					pattern = @"%\w{1,16}%";
  1400. 					regex = new Regex( pattern );
  1401. 					if ( regex.IsMatch( line ) )
  1402. 					{
  1403. 						string envvar = regex.Match( line ).Value.ToUpper( );
  1404. 						string varname = envvar.Substring( 1, envvar.Length - 2 );
  1405. 						// Skip warning if we are dealing with a (dynamic) system variable
  1406. 						pattern = string.Format( @"^({0}|{1})$", dynvarpattern, sysvarpattern );
  1407. 						if ( !new Regex( pattern, ignorecase ).IsMatch( varname ) )
  1408. 						{
  1409. 							message = string.Format( "Variable \"{0}\" found with delayed variable expansion enabled; shouldn't that be \"!{1}!\" instead?", envvar, varname );
  1410. 							// Skip warning if line is a comment
  1411. 							if ( !new Regex( @"(^|&)\s*(REM($|\s)|::)", ignorecase ).IsMatch( line ) )
  1412. 							{
  1413. 								allwarnings += string.Format( "{0}Line {1,5:0}:\t{2}", newline, linenum, message );
  1414. 								warnmsg += string.Format( "{0}{1}.", newline, message );
  1415. 								errinline = true;
  1416. 							}
  1417. 						}
  1418. 					}
  1419. 				}
  1420. 				else
  1421. 				{
  1422. 					pattern = @"!\w{1,16}!";
  1423. 					regex = new Regex( pattern );
  1424. 					if ( regex.IsMatch( line ) )
  1425. 					{
  1426. 						// Skip warning if line is a comment
  1427. 						if ( !new Regex( @"^\s*(REM($|\s)|::)", ignorecase ).IsMatch( line ) )
  1428. 						{
  1429. 							if ( delayedvarglobal )
  1430. 							{
  1431. 								message = string.Format( "\"{0}\" looks like a delayed variable, check if delayed variable expansion is enabled", regex.Match( line ).Value.ToUpper( ) );
  1432. 								allbadpr += string.Format( "{0}Line {1,5:0}:\t{2}", newline, linenum, message );
  1433. 								badprmsg += string.Format( "{0}{1}.", newline, message );
  1434. 							}
  1435. 							else
  1436. 							{
  1437. 								message = string.Format( "\"{0}\" looks like a delayed variable, but delayed variable expansion is not enabled", regex.Match( line ).Value.ToUpper( ) );
  1438. 								allerrors += string.Format( "{0}Line {1,5:0}:\t{2}", newline, linenum, message );
  1439. 								errormsg += string.Format( "{0}{1}.", newline, message );
  1440. 							}
  1441. 							errinline = true;
  1442. 						}
  1443. 					}
  1444. 				}
  1445.  
  1446. 				#endregion Delayed variables without delayed expansion
  1447.  
  1448. 				// Excess number of delimiters in FOR /F loop
  1449. 				#region Too many delimiters
  1450.  
  1451. 				pattern = @"(?<!\w)FOR\s+/F\s+\""(?:[^""]+\s)?delims=([^\s\)\""]{5,})(?:\s[^""]+)?\""";
  1452. 				regex = new Regex( pattern, ignorecase );
  1453. 				if ( regex.IsMatch( line ) )
  1454. 				{
  1455. 					string delims = regex.Match( line ).Groups[1].ToString( );
  1456. 					List<char> nodups = new List<char>( );
  1457. 					foreach ( char del in delims )
  1458. 					{
  1459. 						if ( !nodups.Contains( del ) )
  1460. 						{
  1461. 							nodups.Add( del );
  1462. 						}
  1463. 					}
  1464. 					if ( nodups.Count == delims.Length )
  1465. 					{
  1466. 						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 );
  1467. 						alltips += string.Format( "{0}Line {1,5:0}:\t{2}", newline, linenum, message );
  1468. 						tipsmsg += string.Format( "{0}{1}.", newline, message );
  1469. 					}
  1470. 					else
  1471. 					{
  1472. 						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 );
  1473. 						allerrors += string.Format( "{0}Line {1,5:0}:\t{2}", newline, linenum, message );
  1474. 						errormsg += string.Format( "{0}{1}.", newline, message );
  1475. 					}
  1476. 					errinline = true;
  1477. 				}
  1478. 				else
  1479. 				{
  1480. 					pattern = @"(?<!\w)FOR\s+/F\s+\""(?:[^""]+\s)?delims=([^\s\)\""]+)(?:\s[^""]+)?\""";
  1481. 					regex = new Regex( pattern, ignorecase );
  1482. 					if ( regex.IsMatch( line ) )
  1483. 					{
  1484. 						string delims = regex.Match( line ).Groups[1].ToString( );
  1485. 						List<char> nodups = new List<char>( );
  1486. 						foreach ( char del in delims )
  1487. 						{
  1488. 							if ( !nodups.Contains( del ) )
  1489. 							{
  1490. 								nodups.Add( del );
  1491. 							}
  1492. 						}
  1493. 						if ( delims.Length > nodups.Count )
  1494. 						{
  1495. 							message = string.Format( "Found {0} duplicate delimiters (delims={1}) in a FOR /F loop", delims.Length - nodups.Count, delims );
  1496. 							allerrors += string.Format( "{0}Line {1,5:0}:\t{2}", newline, linenum, message );
  1497. 							errormsg += string.Format( "{0}{1}.", newline, message );
  1498. 							errinline = true;
  1499. 						}
  1500. 					}
  1501. 				}
  1502.  
  1503. 				#endregion Too many delimiters
  1504.  
  1505. 				// Wildcards in IF comparison
  1506. 				#region Wildcards in IF comparison
  1507.  
  1508. 				pattern = @"\bIF\s+(?:/I\s+)?(?:NOT\s+)?\""%\w{1,16}%\""\s*==\s*\""[^""\*\?]*[\*\?]";
  1509. 				regex = new Regex( pattern, ignorecase );
  1510. 				if ( regex.IsMatch( line ) )
  1511. 				{
  1512. 					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";
  1513. 					alltips += string.Format( "{0}Line {1,5:0}:\t{2}", newline, linenum, message );
  1514. 					tipsmsg += string.Format( "{0}{1}.", newline, message );
  1515. 					errinline = true;
  1516. 				}
  1517.  
  1518. 				#endregion Wildcards in IF comparison
  1519.  
  1520. 				// ELSE on new line
  1521. 				#region ELSE on new line
  1522.  
  1523. 				pattern = @"(^|\n)\s*ELSE";
  1524. 				regex = new Regex( pattern, ignorecase );
  1525. 				if ( regex.IsMatch( line ) )
  1526. 				{
  1527. 					// Missing error message and incorrect results reported by Bob Berry
  1528. 					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";
  1529. 					alltips += string.Format( "{0}Line {1,5:0}:\t{2}", newline, linenum, message );
  1530. 					tipsmsg += string.Format( "{0}{1}.", newline, message );
  1531. 					errinline = true;
  1532. 				}
  1533.  
  1534. 				#endregion ELSE on new line
  1535.  
  1536. 				// count SETLOCAL and ENDLOCAL pairs
  1537. 				#region SETLOCAL ENDLOCAL pairs
  1538.  
  1539. 				pattern = @"^\s*SETLOCAL\b";
  1540. 				regex = new Regex( pattern, ignorecase );
  1541. 				if ( regex.IsMatch( line ) )
  1542. 				{
  1543. 					setlocal += 1;
  1544. 				}
  1545. 				if ( setlocal > 1 )
  1546. 				{
  1547. 					message = "Found a nested SETLOCAL";
  1548. 					if ( allwarnings.IndexOf( message ) > -1 )
  1549. 					{
  1550. 						allwarnings += string.Format( "{0}Line {1,5:0}:\t{2}", newline, linenum, message );
  1551. 						warnmsg += string.Format( "{0}{1}.", newline, message );
  1552. 					}
  1553. 					errinline = true;
  1554. 				}
  1555.  
  1556. 				pattern = @"^\s*ENDLOCAL\b";
  1557. 				regex = new Regex( pattern, ignorecase );
  1558. 				if ( regex.IsMatch( line ) )
  1559. 				{
  1560. 					setlocal -= 1;
  1561. 				}
  1562. 				if ( setlocal < 0 )
  1563. 				{
  1564. 					message = "Number of ENDLOCALs exceeds number of SETLOCALs";
  1565. 					allwarnings += string.Format( "{0}Line {1,5:0}:\t{2}", newline, linenum, message );
  1566. 					warnmsg += string.Format( "{0}{1}.", newline, message );
  1567. 					errinline = true;
  1568. 				}
  1569.  
  1570. 				#endregion SETLOCAL ENDLOCAL pairs
  1571.  
  1572. 				// Whitespace and equal signs in IF statements
  1573. 				#region Whitespace and equal signs in IF statements
  1574.  
  1575. 				pattern = @"(^|\b)IF\s+(/I\s+)?(?:\w+|\""[^""]+\"")\s*==?\s*(?:\w+|\""[^""]+\"")\s*[\(\w]";
  1576. 				regex = new Regex( pattern, ignorecase );
  1577. 				if ( regex.IsMatch( line ) )
  1578. 				{
  1579. 					// Single equal sign
  1580. 					pattern = @"(^|\b)IF\s+(/I\s+)?(?:\w+|\""[^""]+\"")\s*=\s*(?:\w+|\""[^""]+\"")\s*[\(\w]";
  1581. 					regex = new Regex( pattern, RegexOptions.IgnoreCase );
  1582. 					if ( regex.IsMatch( line ) )
  1583. 					{
  1584. 						message = "Single equal sign in IF statement, where a double equal sign is required";
  1585. 						allerrors += string.Format( "{0}Line {1,5:0}:\t{2}", newline, linenum, message );
  1586. 						errormsg += string.Format( "{0}{1}.", newline, message );
  1587. 						errinline = true;
  1588. 					}
  1589. 					// Whitespace before code block
  1590. 					pattern = @"(?:^|\b)(IF\s+(/I\s)?\s*(\w+|\""[^""]+\"")\s*==?\s*(\w+|\""[^""]+\"")\()";
  1591. 					regex = new Regex( pattern, ignorecase );
  1592. 					if ( regex.IsMatch( line ) )
  1593. 					{
  1594. 						MatchCollection matches = regex.Matches( line );
  1595. 						string wrong = matches[0].ToString( );
  1596. 						string correct = string.Format( "IF {0}{1}=={2} (", matches[0].Groups[2].ToString( ).ToUpper( ), matches[0].Groups[3].ToString( ), matches[0].Groups[4].ToString( ) );
  1597. 						message = string.Format( "Insert whitespace after the IF comparison, before the opening parenthesis of the code block: {0}...) instead of {1}...)", correct, wrong );
  1598. 						allerrors += string.Format( "{0}Line {1,5:0}:\t{2}", newline, linenum, message );
  1599. 						errormsg += string.Format( "{0}{1}.", newline, message );
  1600. 						errinline = true;
  1601. 					}
  1602. 				}
  1603.  
  1604. 				#endregion Whitespace and equal signs in IF statements
  1605. 			}
  1606. 		}
  1607.  
  1608.  
  1609. 		public static void CheckLinefeeds( )
  1610. 		{
  1611. 			string testlinefeeds = allsrc.Replace( "\r\n", "" );
  1612. 			if ( testlinefeeds.Contains( "\r" ) || testlinefeeds.Contains( "\n" ) )
  1613. 			{
  1614. 				int linecount = 0;
  1615. 				foreach ( string line in allsrc.Split( new string[] { "\r\n" }, StringSplitOptions.None ) )
  1616. 				{
  1617. 					linecount += 1;
  1618. 					if ( line.Contains( "\r" ) || line.Contains( "\n" ) )
  1619. 					{
  1620. 						incorrectlinefeeds.Add( linecount );
  1621. 					}
  1622. 				}
  1623. 			}
  1624. 		}
  1625.  
  1626.  
  1627. 		public static void CheckUndefinedVariables( string line )
  1628. 		{
  1629. 			// Ignore if test for undefined variables is excluded
  1630. 			if ( checkundef )
  1631. 			{
  1632. 				string pattern;
  1633. 				Regex regex;
  1634.  
  1635. 				// Skip this test if variable is inside comment
  1636. 				pattern = @"^\s*(::|REM\s)";
  1637. 				regex = new Regex( pattern, ignorecase );
  1638. 				if ( !regex.IsMatch( line ) )
  1639. 				{
  1640. 					// This regex pattern catches "straight" variables as well as "nested" ones
  1641. 					pattern = @"((?<!%)%{1,2}|(?:\^!)?!)([\w\.-]{1," + maxvarlen + @"})(\1|:)";
  1642. 					regex = new Regex( pattern, ignorecase );
  1643. 					if ( regex.IsMatch( line ) )
  1644. 					{
  1645. 						MatchCollection matches = regex.Matches( line );
  1646. 						foreach ( Match match in matches )
  1647. 						{
  1648. 							string envvar = match.Groups[2].ToString( );
  1649. 							if ( !upcaseenvvars.Contains( envvar.ToUpper( ) ) )
  1650. 							{
  1651. 								if ( !envvarerrors.Contains( envvar.ToUpper( ) ) )
  1652. 								{
  1653. 									envvarerrors.Add( envvar.ToUpper( ) );
  1654. 								}
  1655. 								if ( includelocalvars || !localenvvars.Contains( envvar.ToUpper( ) ) )
  1656. 								{
  1657. 									if ( mysteryvars > 0 )
  1658. 									{
  1659. 										if ( mysteryvars == 1 )
  1660. 										{
  1661. 											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 );
  1662. 										}
  1663. 										else
  1664. 										{
  1665. 											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 );
  1666. 										}
  1667. 										allbadpr += string.Format( "{0}Line {1,5:0}:\t{2}", newline, linenum, message );
  1668. 										badprmsg += string.Format( "{0}{1}.", newline, message );
  1669. 									}
  1670. 									else
  1671. 									{
  1672. 										message = string.Format( "Undefined variable \"{0}\"", envvar );
  1673. 										allerrors += string.Format( "{0}Line {1,5:0}:\t{2}", newline, linenum, message );
  1674. 										errormsg += string.Format( "{0}{1}.", newline, message );
  1675. 									}
  1676. 								}
  1677. 								else
  1678. 								{
  1679. 									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 );
  1680. 									allbadpr += string.Format( "{0}Line {1,5:0}:\t{2}", newline, linenum, message );
  1681. 									badprmsg += string.Format( "{0}{1}.", newline, message );
  1682. 								}
  1683. 								errinline = true;
  1684. 							}
  1685. 						}
  1686. 					}
  1687. 				}
  1688. 			}
  1689. 		}
  1690.  
  1691.  
  1692. 		public static void CheckVulnerabilities( string line )
  1693. 		{
  1694. 			if ( checkall || checkvulnr )
  1695. 			{
  1696. 				string pattern;
  1697. 				Regex regex;
  1698.  
  1699. 				// Unquoted %CD% code insertion vulnerability
  1700. 				#region Code insertion
  1701. 				if ( !incomment )
  1702. 				{
  1703. 					pattern = @"[^""%!](([%!])(CD|__CD__)(:~\d+(,\d+)?|:[^\2\n\r]+=[^\2\n\r]*)?\2)";
  1704. 					regex = new Regex( pattern, ignorecase );
  1705. 					if ( regex.IsMatch( line ) )
  1706. 					{
  1707. 						string cd = regex.Match( line ).Groups[1].ToString( ).ToUpper( );
  1708. 						message = string.Format( "Unquoted {0} makes the code vulnerable to code insertion (see http://www.thesecurityfactory.be/command-injection-windows.html)", cd );
  1709. 						allbadpr += string.Format( "{0}Line {1,5:0}:\t{2}", newline, linenum, message );
  1710. 						badprmsg += string.Format( "{0}{1}.", newline, message );
  1711. 						errinline = true;
  1712. 					}
  1713. 					else
  1714. 					{
  1715. 						pattern = @"[^""](([%!])([%!])(CD|__CD__)(:~\d+(,\d+)?|:[^\2\3\n\r]+=[^\2\3\n\r]*)?\3\2)";
  1716. 						regex = new Regex( pattern, ignorecase );
  1717. 						if ( regex.IsMatch( line ) )
  1718. 						{
  1719. 							string cd = regex.Match( line ).Groups[1].ToString( ).ToUpper( );
  1720. 							message = string.Format( "Unquoted {0} makes the code vulnerable to code insertion (see http://www.thesecurityfactory.be/command-injection-windows.html)", cd );
  1721. 							allbadpr += string.Format( "{0}Line {1,5:0}:\t{2}", newline, linenum, message );
  1722. 							badprmsg += string.Format( "{0}{1}.", newline, message );
  1723. 							errinline = true;
  1724. 						}
  1725. 					}
  1726. 				}
  1727. 				#endregion Code insertion
  1728.  
  1729. 				// SET /P vulnerability
  1730. 				#region SET /P
  1731.  
  1732. 				pattern = @"(^|\b)SET\s+/P\s+""?\w{1,16}\s*=";
  1733. 				regex = new Regex( pattern, ignorecase );
  1734. 				if ( regex.IsMatch( line ) )
  1735. 				{
  1736. 					message = "SET /P could make your code vulnerable to exploits (see http://www.robvanderwoude.com/battech_inputvalidation.php#SetP)";
  1737. 					allbadpr += string.Format( "{0}Line {1,5:0}:\t{2}", newline, linenum, message );
  1738. 					badprmsg += string.Format( "{0}{1}.", newline, message );
  1739. 					errinline = true;
  1740. 				}
  1741.  
  1742.  
  1743. 				#endregion SET /P
  1744. 			}
  1745. 		}
  1746.  
  1747.  
  1748. 		public static void CloseHTML( )
  1749. 		{
  1750. 			if ( log2html )
  1751. 			{
  1752. 				htmlstream.WriteLine( "</span>" );
  1753. 				htmlstream.WriteLine( );
  1754. 				htmlstream.WriteLine( "</pre>" );
  1755. 				htmlstream.WriteLine( "</body>" );
  1756. 				htmlstream.Write( "</html>" );
  1757. 				htmlstream.Close( );
  1758. 				htmlopen = false;
  1759. 			}
  1760. 		}
  1761.  
  1762.  
  1763. 		public static void CollectEnvironmentVariables( )
  1764. 		{
  1765. 			string pattern;
  1766. 			Regex regex;
  1767.  
  1768. 			// Static system environment variables
  1769. 			foreach ( string envvar in sysvarpattern.Split( '|' ) )
  1770. 			{
  1771. 				//envvars.Add( envvar, 0 );
  1772. 				envvars[envvar] = 0;
  1773. 				upcaseenvvars.Add( envvar.ToUpper( ) );
  1774. 			}
  1775.  
  1776. 			// Dynamic system environment variables
  1777. 			foreach ( string envvar in dynvarpattern.Split( '|' ) )
  1778. 			{
  1779. 				//envvars.Add( envvar, 0 );
  1780. 				envvars[envvar] = 0;
  1781. 				upcaseenvvars.Add( envvar.ToUpper( ) );
  1782. 			}
  1783.  
  1784. 			// Environment variables SET in the source code with "plain" SET statement
  1785. 			pattern = @"(?:^|\b)SET\s+(\w{1," + maxvarlen + @"})=";
  1786. 			regex = new Regex( pattern, ignorecase );
  1787. 			if ( regex.IsMatch( allsrc_nocomments ) )
  1788. 			{
  1789. 				foreach ( Match match in regex.Matches( allsrc_nocomments ) )
  1790. 				{
  1791. 					if ( !upcaseenvvars.Contains( match.Groups[1].ToString( ).ToUpper( ) ) )
  1792. 					{
  1793. 						//envvars.Add( match.Groups[1].ToString( ), 0 );
  1794. 						envvars[match.Groups[1].ToString( )] = 0;
  1795. 						upcaseenvvars.Add( match.Groups[1].ToString( ).ToUpper( ) );
  1796. 					}
  1797. 				}
  1798. 			}
  1799.  
  1800. 			// Environment variables SET in the source code with SET /A or SET /P statement
  1801. 			pattern = @"(?:^|\b)SET\s+(?:/[AP]\s+)?""?(\w{1," + maxvarlen + @"})\s*=";
  1802. 			regex = new Regex( pattern, ignorecase );
  1803. 			if ( regex.IsMatch( allsrc_nocomments ) )
  1804. 			{
  1805. 				foreach ( Match match in regex.Matches( allsrc_nocomments ) )
  1806. 				{
  1807. 					if ( !upcaseenvvars.Contains( match.Groups[1].ToString( ).ToUpper( ) ) )
  1808. 					{
  1809. 						//envvars.Add( match.Groups[1].ToString( ), 0 );
  1810. 						envvars[match.Groups[1].ToString( )] = 0;
  1811. 						upcaseenvvars.Add( match.Groups[1].ToString( ).ToUpper( ) );
  1812. 					}
  1813. 				}
  1814. 			}
  1815.  
  1816. 			// Take into account lines like SET %%A or SET %Var%=...
  1817. 			pattern = @"(^|\b)\s*SET\s+[%!]";
  1818. 			regex = new Regex( pattern, ignorecase );
  1819. 			if ( regex.IsMatch( allsrc_nocomments ) )
  1820. 			{
  1821. 				MatchCollection matches = regex.Matches( allsrc_nocomments );
  1822. 				mysteryvars = matches.Count;
  1823. 			}
  1824.  
  1825. 			// Include local environment variables if /E command line switch was used
  1826. 			if ( includelocalvars )
  1827. 			{
  1828. 				foreach ( string envvar in Environment.GetEnvironmentVariables( ).Keys )
  1829. 				{
  1830. 					if ( !upcaseenvvars.Contains( envvar ) )
  1831. 					{
  1832. 						//envvars.Add( envvar, 0 );
  1833. 						envvars[envvar] = 0;
  1834. 						upcaseenvvars.Add( envvar.ToUpper( ) );
  1835. 					}
  1836. 				}
  1837. 			}
  1838.  
  1839. 			// Check how often a variable is used
  1840. 			// Temporarily clone the keys into a new array because we are going to modify the dictionary while iterating through its keys
  1841. 			string[] envvarskeys = envvars.Keys.ToArray( );
  1842. 			foreach ( string envvar in envvarskeys )
  1843. 			{
  1844. 				string substringpattern = @"~\-?\d+(,\d+)?";
  1845. 				string replacepattern = @"[^\1\=]{1,127}=[^\1\=]";
  1846. 				pattern = string.Format( @"(%|!){0}(\:({1}|{2}))\1", envvar, substringpattern, replacepattern );
  1847. 				regex = new Regex( pattern, ignorecase );
  1848. 				int used = regex.Matches( allsrc_nocomments ).Count;
  1849. 				envvars[envvar] = used;
  1850. 			}
  1851.  
  1852. 			// All loacal environment variables
  1853. 			foreach ( string envvar in Environment.GetEnvironmentVariables( ).Keys )
  1854. 			{
  1855. 				if ( !localenvvars.Contains( envvar.ToUpper( ) ) )
  1856. 				{
  1857. 					localenvvars.Add( envvar.ToUpper( ) );
  1858. 				}
  1859. 			}
  1860. 		}
  1861.  
  1862.  
  1863. 		public static void CollectLabels( )
  1864. 		{
  1865. 			string pattern;
  1866. 			Regex regex;
  1867.  
  1868. 			pattern = @"(?:^|\n|\r)\s*:([\w\.\[\]/+\-\(\)\{\}]{1,127})";
  1869. 			regex = new Regex( pattern );
  1870. 			if ( regex.IsMatch( allsrc_nocomments ) )
  1871. 			{
  1872. 				foreach ( Match match in regex.Matches( allsrc_nocomments ) )
  1873. 				{
  1874. 					string label = match.Groups[1].ToString( );
  1875. 					// Find the number of GOTOs to this label
  1876. 					pattern = string.Format( @"(?<=^|\s|\b|\()GOTO[ \t]+:?{0}(?=\n|\r|[ \t]+[\n\r]|\)|$)", label );
  1877. 					regex = new Regex( pattern, ignorecase );
  1878. 					int jumps = regex.Matches( allsrc_nocomments ).Count;
  1879. 					//labels.Add( label, jumps );
  1880. 					labels[label] = jumps;
  1881. 					upcaselabels.Add( label.ToUpper( ) );
  1882. 				}
  1883. 			}
  1884. 		}
  1885.  
  1886.  
  1887. 		public static void CollectSubroutines( )
  1888. 		{
  1889. 			// For each label, check if it is a subroutine label
  1890. 			foreach ( string label in labels.Keys )
  1891. 			{
  1892. 				if ( subroutines.IndexOfKey( label ) == -1 )
  1893. 				{
  1894. 					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 );
  1895. 					Regex regex = new Regex( pattern, ignorecase );
  1896. 					if ( regex.IsMatch( allsrc_nocomments ) )
  1897. 					{
  1898. 						MatchCollection matches = regex.Matches( allsrc_nocomments );
  1899. 						foreach ( Match match in matches )
  1900. 						{
  1901. 							string pattern2 = @"(?=\n|\r|^)\s*:([\w\.\[\]/+\-\(\)\{\}]{1,127})(?=\n|\r|\s|$)";
  1902. 							Regex regex2 = new Regex( pattern2, ignorecase );
  1903. 							string match2 = match.Groups[1].ToString( );
  1904. 							if ( !regex2.IsMatch( match2 ) )
  1905. 							{
  1906. 								//subroutines.Add( label, 0 );
  1907. 								subroutines[label] = 0;
  1908. 							}
  1909. 						}
  1910. 					}
  1911. 				}
  1912. 			}
  1913.  
  1914. 			// Find the number of CALLs to each subroutine
  1915. 			foreach ( string subroutine in subroutines.Keys )
  1916. 			{
  1917. 				string pattern = string.Format( @"(?<=^|\n|\r|\s|\b|&\()CALL[ \t]+:{{0}}(?=\n|\r|\s|\b|&|\)|$)" );
  1918. 				Regex regex = new Regex( pattern, ignorecase );
  1919. 				if ( regex.IsMatch( allsrc_nocomments ) )
  1920. 				{
  1921. 					MatchCollection matches = regex.Matches( allsrc_nocomments );
  1922. 					subroutines[subroutine] = matches.Count;
  1923. 				}
  1924. 			}
  1925. 		}
  1926.  
  1927.  
  1928. 		public static string ConsoleColor2CSSColor( ConsoleColor color )
  1929. 		{
  1930. 			return ConsoleColor2CSSColor( color.ToString( ) );
  1931. 		}
  1932.  
  1933.  
  1934. 		public static string ConsoleColor2CSSColor( string color )
  1935. 		{
  1936. 			string csscolor = color.ToLower( );
  1937. 			switch ( csscolor )
  1938. 			{
  1939. 				case "darkyellow": // darkyellow is not a valid CSS value, reported and fixed by Alfons van Zwol
  1940. 					csscolor = "#808000";
  1941. 					break;
  1942. 			}
  1943. 			return csscolor;
  1944. 		}
  1945.  
  1946.  
  1947. 		public static void CreateHTML( )
  1948. 		{
  1949. 			if ( log2html )
  1950. 			{
  1951. 				htmlstream = new StreamWriter( htmlfile );
  1952. 				htmlopen = true;
  1953. 				htmlstream.WriteLine( "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">" );
  1954. 				htmlstream.WriteLine( "<html>" );
  1955. 				htmlstream.WriteLine( "<head>" );
  1956. 				htmlstream.WriteLine( "<title>BatCodeCheck Version {0} report for {1}</title>", progver, Path.GetFileName( source ) );
  1957. 				htmlstream.WriteLine( "<style type=\"text/css\">" );
  1958. 				htmlstream.WriteLine( "body" );
  1959. 				htmlstream.WriteLine( "{" );
  1960. 				htmlstream.WriteLine( "\tbackground-color: {0};", windowbgcolor.ToString( ).ToLower( ) );
  1961. 				htmlstream.WriteLine( "\tcolor: {0};", windowfgcolor.ToString( ).ToLower( ) );
  1962. 				htmlstream.WriteLine( "}" );
  1963. 				htmlstream.WriteLine( "</style>" );
  1964. 				htmlstream.WriteLine( "</head>" );
  1965. 				htmlstream.WriteLine( "<body>" );
  1966. 				htmlstream.WriteLine( "<pre>" );
  1967. 				htmlstream.WriteLine( "<span style=\"color: white;\">Time          :  {0}", DateTime.Now.ToString( "yyyy-MM-dd HH:mm:ss" ) );
  1968. 				htmlstream.WriteLine( "Program       :  BatCodeCheck, Version {0}", progver );
  1969. 				htmlstream.WriteLine( "Arguments     :  {0}", allargs );
  1970. 				htmlstream.WriteLine( "File name     :  \"{0}\"", Path.GetFullPath( source ) );
  1971. 				htmlstream.WriteLine( "File date     :  {0}", File.GetLastWriteTime( source ).ToString( "yyyy-MM-dd HH:mm:ss" ) );
  1972. 				htmlstream.WriteLine( "File encoding :  {0}", encoding );
  1973. 				htmlstream.WriteLine( "Tests         :  {0}", checks );
  1974. 				foreach ( char check in checks )
  1975. 				{
  1976. 					string test = check.ToString( ).ToUpper( );
  1977. 					htmlstream.WriteLine( "                 {0} =  {1}", test, tests[test] );
  1978. 				}
  1979. 				if ( envvarerrors.Count > 0 )
  1980. 				{
  1981. 					htmlstream.Write( "Local environment variables are {1}", ( includelocalvars ? "included" : "ignored" ) );
  1982. 				}
  1983. 				htmlstream.WriteLine( "</span>\n" );
  1984. 			}
  1985. 		}
  1986.  
  1987.  
  1988. 		public static void DisplayLineNumber( int num )
  1989. 		{
  1990. 			if ( dispnums )
  1991. 			{
  1992. 				Console.BackgroundColor = linenumbgcolor;
  1993. 				Console.ForegroundColor = linenumfgcolor;
  1994. 				if ( num == lastline )
  1995. 				{
  1996. 					Console.Write( "{0,4} ", string.Empty );
  1997. 					WriteLineHTML( string.Format( "{0,4} ", string.Empty ) );
  1998. 				}
  1999. 				else
  2000. 				{
  2001. 					Console.Write( "{0,4} ", num );
  2002. 					WriteLineHTML( string.Format( "{0,4} ", num ) );
  2003. 				}
  2004. 				Console.ResetColor( );
  2005. 				Console.BackgroundColor = windowbgcolor;
  2006. 				Console.ForegroundColor = windowfgcolor;
  2007. 				lastline = num;
  2008. 			}
  2009. 		}
  2010.  
  2011.  
  2012. 		public static void DisplayMessagesPerLine( string line )
  2013. 		{
  2014. 			bool padline = true;
  2015. 			string blankline = new String( ' ', Console.WindowWidth );
  2016. 			string msgline;
  2017.  
  2018. 			if ( errinline )
  2019. 			{
  2020. 				if ( lasterrline != linenum )
  2021. 				{
  2022. 					lineswitherrors += 1;
  2023. 				}
  2024. 				foreach ( string error in errormsg.Split( "\n\r".ToCharArray( ) ) )
  2025. 				{
  2026. 					if ( !String.IsNullOrWhiteSpace( error ) )
  2027. 					{
  2028. 						if ( wrap )
  2029. 						{
  2030. 							msgline = WordWrap( ( error ).Replace( "\t", " ERROR:   " ), padline );
  2031. 						}
  2032. 						else
  2033. 						{
  2034. 							msgline = ( error + blankline ).Replace( "\t", " ERROR:   " ).Substring( 0, Console.WindowWidth - 1 );
  2035. 						}
  2036. 						Console.BackgroundColor = errorbgcolor;
  2037. 						Console.ForegroundColor = errorfgcolor;
  2038. 						Console.Write( msgline );
  2039. 						WriteLineHTML( msgline );
  2040. 						Console.ResetColor( );
  2041. 						Console.WriteLine( );
  2042. 					}
  2043. 				}
  2044. 				foreach ( string warning in warnmsg.Split( "\n\r".ToCharArray( ) ) )
  2045. 				{
  2046. 					if ( !String.IsNullOrWhiteSpace( warning ) )
  2047. 					{
  2048. 						if ( wrap )
  2049. 						{
  2050. 							msgline = WordWrap( ( warning ).Replace( "\t", " WARNING: " ), padline );
  2051. 						}
  2052. 						else
  2053. 						{
  2054. 							msgline = ( warning + blankline ).Replace( "\t", " WARNING: " ).Substring( 0, Console.WindowWidth - 1 );
  2055. 						}
  2056. 						Console.BackgroundColor = warningbgcolor;
  2057. 						Console.ForegroundColor = warningfgcolor;
  2058. 						Console.Write( msgline );
  2059. 						WriteLineHTML( msgline );
  2060. 						Console.ResetColor( );
  2061. 						Console.WriteLine( );
  2062. 					}
  2063. 				}
  2064. 				foreach ( string badpractice in badprmsg.Split( "\n\r".ToCharArray( ) ) )
  2065. 				{
  2066. 					if ( !String.IsNullOrWhiteSpace( badpractice ) )
  2067. 					{
  2068. 						if ( wrap )
  2069. 						{
  2070. 							msgline = WordWrap( ( badpractice ).Replace( "\t", " WARNING: " ), padline );
  2071. 						}
  2072. 						else
  2073. 						{
  2074. 							msgline = ( badpractice + blankline ).Replace( "\t", " WARNING: " ).Substring( 0, Console.WindowWidth - 1 );
  2075. 						}
  2076. 						Console.BackgroundColor = badprbgcolor;
  2077. 						Console.ForegroundColor = badprfgcolor;
  2078. 						Console.Write( msgline );
  2079. 						WriteLineHTML( msgline );
  2080. 						Console.ResetColor( );
  2081. 						Console.WriteLine( );
  2082. 					}
  2083. 				}
  2084. 				foreach ( string tip in tipsmsg.Split( "\n\r".ToCharArray( ) ) )
  2085. 				{
  2086. 					if ( !String.IsNullOrWhiteSpace( tip ) )
  2087. 					{
  2088. 						if ( wrap )
  2089. 						{
  2090. 							msgline = WordWrap( ( tip ).Replace( "\t", " TIP:     " ), padline );
  2091. 						}
  2092. 						else
  2093. 						{
  2094. 							msgline = ( tip + blankline ).Replace( "\t", " TIP:     " ).Substring( 0, Console.WindowWidth - 1 );
  2095. 						}
  2096. 						Console.BackgroundColor = tipsbgcolor;
  2097. 						Console.ForegroundColor = tipsfgcolor;
  2098. 						Console.Write( msgline );
  2099. 						WriteLineHTML( msgline );
  2100. 						Console.ResetColor( );
  2101. 						Console.WriteLine( );
  2102. 					}
  2103. 				}
  2104. 				errinline = false;
  2105. 				lasterrline = linenum;
  2106. 				rc = linenum;
  2107. 			}
  2108. 		}
  2109.  
  2110.  
  2111. 		public static void DisplayMessagesSummary( )
  2112. 		{
  2113. 			// Encoding
  2114. 			if ( encoding != "ASCII" )
  2115. 			{
  2116. 				allerrsum += string.Format( "{0}The batch file \"{1}\" is stored in {2} encoding, whereas it should be in ASCII", newline, source, encoding );
  2117. 			}
  2118. 			// Summary
  2119. 			if ( lineswitherrors == 1 )
  2120. 			{
  2121. 				allerrsum += string.Format( "{0}1   line generated a warning and should be examined", newline );
  2122. 			}
  2123. 			else if ( lineswitherrors > 1 )
  2124. 			{
  2125. 				allerrsum += string.Format( "{0}{1,-3} lines generated warnings and should be examined", newline, lineswitherrors );
  2126. 			}
  2127. 			// Incorrect linefeeds
  2128. 			if ( incorrectlinefeeds.Count == 1 )
  2129. 			{
  2130. 				allerrsum += String.Format( "{0}1   line is terminated with improper linefeeds (line {1})", newline, incorrectlinefeeds[0] );
  2131. 			}
  2132. 			if ( incorrectlinefeeds.Count > 1 )
  2133. 			{
  2134. 				allerrsum += String.Format( "{0}{1,-3} lines are terminated with improper linefeeds (lines {2})", newline, incorrectlinefeeds.Count, string.Join( ", ", incorrectlinefeeds.ToArray( ) ) );
  2135. 			}
  2136. 			// Unterminated %
  2137. 			if ( unterminatedpercentsigns == 1 )
  2138. 			{
  2139. 				allerrsum += string.Format( "{0}1   unterminated percent sign was found", newline );
  2140. 			}
  2141. 			else if ( unterminatedpercentsigns > 1 )
  2142. 			{
  2143. 				allerrsum += string.Format( "{0}{1,-3} unterminated percent signs were found", newline, unterminatedpercentsigns );
  2144. 			}
  2145. 			// Unterminated !
  2146. 			if ( unterminatedexclamations == 1 )
  2147. 			{
  2148. 				allerrsum += string.Format( "{0}1   unterminated exclamation mark was found while delayed variable expansion was enabled", newline );
  2149. 			}
  2150. 			else if ( unterminatedexclamations > 1 )
  2151. 			{
  2152. 				allerrsum += string.Format( "{0}{1,-3} unterminated exclamation marks were found while delayed variable expansion was enabled", newline, unterminatedexclamations );
  2153. 			}
  2154. 			// Unterminated "
  2155. 			if ( unterminateddoublequotes == 1 )
  2156. 			{
  2157. 				allerrsum += string.Format( "{0}1   unterminated doublequote was found", newline );
  2158. 			}
  2159. 			else if ( unterminateddoublequotes > 1 )
  2160. 			{
  2161. 				allerrsum += string.Format( "{0}{1,-3} unterminated doublequotes were found", newline, unterminateddoublequotes );
  2162. 			}
  2163. 			// Unterminated '
  2164. 			if ( unterminatedsinglequotes == 1 )
  2165. 			{
  2166. 				allerrsum += string.Format( "{0}1   unterminated singlequote was found in a FOR /F loop", newline );
  2167. 			}
  2168. 			else if ( unterminatedsinglequotes > 1 )
  2169. 			{
  2170. 				allerrsum += string.Format( "{0}{1,-3} unterminated singlequotes were found in FOR /F loops", newline, unterminatedsinglequotes );
  2171. 			}
  2172. 			// Undefined environment variables
  2173. 			string ignoredmsg = ( includelocalvars ? string.Empty : " (ignoring local environment variables)" );
  2174. 			if ( envvarerrors.Count == 1 )
  2175. 			{
  2176. 				allerrsum += string.Format( "{0}1   undefined variable was detected{1}: {2}", newline, ignoredmsg, envvarerrors[0] );
  2177. 			}
  2178. 			else if ( envvarerrors.Count > 1 )
  2179. 			{
  2180. 				allerrsum += string.Format( "{0}{1,-3} undefined variables were detected{2}: {3}", newline, envvarerrors.Count, ignoredmsg, string.Join( ", ", envvarerrors ) );
  2181. 			}
  2182. 			// Undefined labels
  2183. 			if ( gotoerrors.Count == 1 )
  2184. 			{
  2185. 				allerrsum += string.Format( "{0}1   label was missing: {1}", newline, gotoerrors[0] );
  2186. 			}
  2187. 			else if ( gotoerrors.Count > 1 )
  2188. 			{
  2189. 				allerrsum += string.Format( "{0}{1,-3} labels were missing: {2}", newline, gotoerrors.Count, string.Join( ", ", gotoerrors ) );
  2190. 			}
  2191. 			// Undefined subroutines
  2192. 			if ( callerrors.Count == 1 )
  2193. 			{
  2194. 				allerrsum += string.Format( "{0}1   subroutine label was missing (note that subroutine labels are case sensitive): {1}", newline, callerrors[0] );
  2195. 			}
  2196. 			else if ( callerrors.Count > 1 )
  2197. 			{
  2198. 				allerrsum += string.Format( "{0}{1,-3} subroutine labels were missing (note that subroutine labels are case sensitive): {2}", newline, callerrors.Count, string.Join( ", ", callerrors ) );
  2199. 			}
  2200. 			// Obsolete commands
  2201. 			if ( obsoletecmd == 1 )
  2202. 			{
  2203. 				allerrsum += string.Format( "{0}1   obsolete or 16-bit command was found", newline );
  2204. 			}
  2205. 			else if ( obsoletecmd > 1 )
  2206. 			{
  2207. 				allerrsum += string.Format( "{0}{1,-3} obsolete and/or 16-bit commands were found", newline, obsoletecmd );
  2208. 			}
  2209. 			// Unterminated parentheses
  2210. 			if ( parenthesis > 0 )
  2211. 			{
  2212. 				allerrsum += string.Format( "{0}Opening parenthesis outnumber closing parenthesis by {1}", newline, parenthesis );
  2213. 			}
  2214. 			else if ( parenthesis < 0 )
  2215. 			{
  2216. 				allerrsum += string.Format( "{0}Closing parenthesis outnumber opening parenthesis by {1}", newline, Math.Abs( parenthesis ) );
  2217. 			}
  2218. 			// Unterminated SETLOCALs
  2219. 			if ( setlocal > 0 )
  2220. 			{
  2221. 				allerrsum += string.Format( "{0}SETLOCALs outnumber ENDLOCALs by {1}", newline, setlocal );
  2222. 			}
  2223. 			else if ( setlocal < 0 )
  2224. 			{
  2225. 				allerrsum += string.Format( "{0}ENDLOCALs outnumber SETLOCALs by {1}", newline, Math.Abs( setlocal ) );
  2226. 			}
  2227.  
  2228. 			// Display warnings if applicable
  2229. 			if ( !String.IsNullOrWhiteSpace( allerrors ) )
  2230. 			{
  2231. 				allerrors = string.Format( "{0}ERRORS / VULNERABILITIES:{1}{0}", newline, allerrors );
  2232. 			}
  2233. 			if ( !String.IsNullOrWhiteSpace( allwarnings ) )
  2234. 			{
  2235. 				allerrors = string.Format( "{0}{1}WARNINGS:{2}{1}", allerrors, newline, allwarnings );
  2236. 			}
  2237. 			if ( !String.IsNullOrWhiteSpace( allbadpr ) )
  2238. 			{
  2239. 				allerrors = string.Format( "{0}{1}RISKY CODE:{2}{1}", allerrors, newline, allbadpr );
  2240. 			}
  2241. 			if ( !String.IsNullOrWhiteSpace( alltips ) )
  2242. 			{
  2243. 				allerrors = string.Format( "{0}{1}TIPS / BEST PRACTICES:{2}{1}", allerrors, newline, alltips );
  2244. 			}
  2245. 			if ( !String.IsNullOrWhiteSpace( allerrsum ) )
  2246. 			{
  2247. 				allerrors = string.Format( "{0}{1}SUMMARY:{2}{1}", allerrors, newline, allerrsum );
  2248. 			}
  2249.  
  2250. 			Console.Write( VerboseSummary( ) );
  2251.  
  2252. 			if ( !String.IsNullOrEmpty( allerrors ) )
  2253. 			{
  2254. 				allerrors += string.Format( "{0}{0}Note that some warnings are only displayed once. Correct the errors and run this test again.", newline );
  2255. 				Console.ResetColor( );
  2256. 				Console.WriteLine( newline );
  2257. 				Console.WriteLine( "Time          :  {0}", DateTime.Now.ToString( "yyyy-MM-dd HH:mm:ss" ) );
  2258. 				Console.WriteLine( "Program       :  BatCodeCheck, Version {0}", progver );
  2259. 				Console.WriteLine( "Arguments     :  {0}", allargs );
  2260. 				Console.WriteLine( "File name     :  \"{0}\"", Path.GetFullPath( source ) );
  2261. 				Console.WriteLine( "File date     :  {0}", File.GetLastWriteTime( source ).ToString( "yyyy-MM-dd HH:mm:ss" ) );
  2262. 				Console.WriteLine( "File encoding :  {0}", encoding );
  2263. 				Console.WriteLine( "Tests         :  {0}", checks );
  2264. 				foreach ( char check in checks )
  2265. 				{
  2266. 					string test = check.ToString( ).ToUpper( );
  2267. 					Console.WriteLine( "                 {0} =  {1}", test, tests[test] );
  2268. 				}
  2269. 				if ( envvarerrors.Count > 0 )
  2270. 				{
  2271. 					Console.WriteLine( "Local environment variables are {0}", ( includelocalvars ? "included" : "ignored" ) );
  2272. 				}
  2273. 				Console.WriteLine( );
  2274. 				Console.BackgroundColor = reportbgcolor;
  2275. 				Console.ForegroundColor = reportfgcolor;
  2276. 				if ( wrap )
  2277. 				{
  2278. 					string[] all_errors = allerrors.Split( "\n\r".ToCharArray( ) );
  2279. 					foreach ( string all_err in all_errors )
  2280. 					{
  2281. 						Console.WriteLine( WordWrap( all_err, false ) );
  2282. 					}
  2283. 				}
  2284. 				else
  2285. 				{
  2286. 					Console.WriteLine( allerrors );
  2287. 				}
  2288. 				WriteLineHTML( allerrors );
  2289. 				Console.ResetColor( );
  2290. 				Console.WriteLine( );
  2291. 				rc = Math.Max( 1, rc );
  2292. 				if ( logging )
  2293. 				{
  2294. 					StreamWriter log = new StreamWriter( logfile, !replacelog );
  2295. 					log.WriteLine( "Time          :  {0}", DateTime.Now.ToString( "yyyy-MM-dd HH:mm:ss" ) );
  2296. 					log.WriteLine( "Program       :  BatCodeCheck, Version {0}", progver );
  2297. 					log.WriteLine( "Arguments     :  {0}", allargs );
  2298. 					log.WriteLine( "File name     :  \"{0}\"", Path.GetFullPath( source ) );
  2299. 					log.WriteLine( "File date     :  {0}", File.GetLastWriteTime( source ).ToString( "yyyy-MM-dd HH:mm:ss" ) );
  2300. 					log.WriteLine( "File encoding :  {0}", encoding );
  2301. 					log.WriteLine( "Tests         :  {0}", checks );
  2302. 					foreach ( char check in checks )
  2303. 					{
  2304. 						string test = check.ToString( ).ToUpper( );
  2305. 						log.WriteLine( "                 {0} =  {1}", test, tests[test] );
  2306. 					}
  2307. 					if ( envvarerrors.Count > 0 )
  2308. 					{
  2309. 						log.WriteLine( "Local environment variables are {0}", ( includelocalvars ? "included" : "ignored" ) );
  2310. 					}
  2311. 					log.WriteLine( "{0}{1}{1}", allerrors, newline );
  2312. 					log.Close( );
  2313. 				}
  2314. 			}
  2315. 		}
  2316.  
  2317.  
  2318. 		public static string EscapedHTML( char chr )
  2319. 		{
  2320. 			switch ( chr )
  2321. 			{
  2322. 				case '&':
  2323. 					return "&amp;";
  2324. 				case '>':
  2325. 					return "&gt;";
  2326. 				case '<':
  2327. 					return "&lt;";
  2328. 				default:
  2329. 					return chr.ToString( );
  2330. 			}
  2331. 		}
  2332.  
  2333.  
  2334. 		public static string GetEncoding( string filename )
  2335. 		{
  2336. 			// Based on code by Jason Pierce (2Toad) on StackOverflow.com
  2337. 			// https://stackoverflow.com/a/19283954
  2338. 			// Determines a text file's encoding by analyzing its byte order mark (BOM).
  2339. 			// Defaults to ASCII when detection of the text file's endianness fails.
  2340.  
  2341. 			// Read the BOM
  2342. 			var bom = new byte[4];
  2343. 			using ( var file = new FileStream( filename, FileMode.Open, FileAccess.Read ) )
  2344. 			{
  2345. 				file.Read( bom, 0, 4 );
  2346. 			}
  2347.  
  2348. 			// Analyze the BOM
  2349. 			if ( bom[0] == 0x2b && bom[1] == 0x2f && bom[2] == 0x76 )
  2350. 				return "UTF-7";
  2351. 			if ( bom[0] == 0xef && bom[1] == 0xbb && bom[2] == 0xbf )
  2352. 				return "UTF-8";
  2353. 			if ( bom[0] == 0xff && bom[1] == 0xfe && bom[2] == 0 && bom[3] == 0 )
  2354. 				return "UTF-32LE";
  2355. 			if ( bom[0] == 0xff && bom[1] == 0xfe )
  2356. 				return "UTF-16LE";
  2357. 			if ( bom[0] == 0xfe && bom[1] == 0xff )
  2358. 				return "UTF-16BE";
  2359. 			if ( bom[0] == 0 && bom[1] == 0 && bom[2] == 0xfe && bom[3] == 0xff )
  2360. 				return "UTF-32BE";
  2361.  
  2362. 			// We actually have no idea what the encoding is if we reach this point, so
  2363. 			// you may wish to return null instead of defaulting to ASCII
  2364. 			return "ASCII";
  2365. 		}
  2366.  
  2367.  
  2368. 		public static List<string> ListInternalCommands( )
  2369. 		{
  2370. 			string comspec = Environment.GetEnvironmentVariable( "COMSPEC" );
  2371. 			StreamReader file = new StreamReader( comspec, Encoding.ASCII );
  2372. 			string content = file.ReadToEnd( );
  2373. 			file.Close( );
  2374.  
  2375. 			List<string> intcmds = new List<string>( );
  2376. 			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";
  2377. 			List<string> exclude = excludelist.Split( ",".ToCharArray( ) ).ToList<string>( );
  2378.  
  2379. 			string pattern = @"([A-Z]\0){2,}";
  2380. 			Regex regex = new Regex( pattern );
  2381. 			if ( regex.IsMatch( content ) )
  2382. 			{
  2383. 				foreach ( Match match in regex.Matches( content ) )
  2384. 				{
  2385. 					string intcmd = match.ToString( ).Replace( "\0", string.Empty );
  2386. 					if ( !exclude.Contains( intcmd ) && !intcmds.Contains( intcmd ) )
  2387. 					{
  2388. 						intcmds.Add( intcmd );
  2389. 					}
  2390. 				}
  2391. 				intcmds.Sort( );
  2392. 			}
  2393. 			if ( intcmds.Count == 0 )
  2394. 			{
  2395. 				// Return a default list if we could not find the internal commands in %COMSPEC%
  2396. 				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";
  2397. 				intcmds = defaultinternalcommands.Split( "|".ToCharArray( ), StringSplitOptions.RemoveEmptyEntries ).ToList<string>( );
  2398. 			}
  2399. 			return intcmds;
  2400. 		}
  2401.  
  2402.  
  2403. 		public static int OpenHTMLFile( )
  2404. 		{
  2405. 			if ( openhtml )
  2406. 			{
  2407. 				try
  2408. 				{
  2409. 					if ( File.Exists( htmlfile ) )
  2410. 					{
  2411. 						Process.Start( htmlfile );
  2412. 					}
  2413. 					else
  2414. 					{
  2415. 						return ShowHelp( string.Format( "Cannot find HTML log file \"{0}\"", htmlfile ) );
  2416. 					}
  2417. 				}
  2418. 				catch ( Exception e )
  2419. 				{
  2420. 					return ShowHelp( e.Message );
  2421. 				}
  2422. 			}
  2423. 			return 0;
  2424. 		}
  2425.  
  2426.  
  2427. 		public static int OpenLogFile( )
  2428. 		{
  2429. 			if ( openlog )
  2430. 			{
  2431. 				try
  2432. 				{
  2433. 					if ( File.Exists( logfile ) )
  2434. 					{
  2435. 						Process.Start( logfile );
  2436. 					}
  2437. 					else
  2438. 					{
  2439. 						return ShowHelp( string.Format( "Cannot find ASCII log file \"{0}\"", logfile ) );
  2440. 					}
  2441. 				}
  2442. 				catch ( Exception e )
  2443. 				{
  2444. 					return ShowHelp( e.Message );
  2445. 				}
  2446. 			}
  2447. 			return 0;
  2448. 		}
  2449.  
  2450.  
  2451. 		public static void ParseLine( string line )
  2452. 		{
  2453. 			string nocomments = StripComments( line );
  2454. 			ResetInlineVariables( );
  2455. 			DisplayLineNumber( linenum );
  2456. 			CheckCommonMistakes( nocomments );
  2457. 			CheckUndefinedVariables( nocomments );
  2458. 			CheckCommandLineArguments( nocomments );
  2459. 			CheckBadPractices( nocomments );
  2460. 			CheckBestPractices( nocomments );
  2461. 			ParseLineAndHighlight( line );
  2462. 			CheckVulnerabilities( nocomments );
  2463. 			DisplayMessagesPerLine( line );
  2464. 		}
  2465.  
  2466.  
  2467. 		public static void ParseLineAndHighlight( string line )
  2468. 		{
  2469. 			// Comments are highlighted and not interpreted at all.
  2470. 			// This may spell trouble in case of multiple commands on a single line.
  2471. 			if ( new Regex( @"^\s*(REM($|\s)|::)", ignorecase ).IsMatch( line ) )
  2472. 			{
  2473. 				string match = new Regex( @"^(\s*(REM($|\s)|::))", ignorecase ).Match( line ).ToString( );
  2474. 				Console.ForegroundColor = intcmdfgcolor;
  2475. 				Console.Write( match );
  2476. 				WriteSpanHTML( match );
  2477. 				Console.ForegroundColor = commentfgscolor;
  2478. 				line = line.Substring( match.Length );
  2479. 				Console.WriteLine( line );
  2480. 				WriteSpanHTML( line );
  2481. 				Console.ResetColor( );
  2482. 			}
  2483. 			// do not highlight ECHO ON or ECHO OFF as ECHOed text
  2484. 			else if ( new Regex( @"^\s*@?ECHO\s+(ON|OFF)\s*$", ignorecase ).IsMatch( line ) )
  2485. 			{
  2486. 				Console.ResetColor( );
  2487. 				Console.WriteLine( line );
  2488. 				WriteSpanHTML( line );
  2489. 			}
  2490. 			// check if delayed variable expansion is enabled (will choke on nested SETLOCALs)
  2491. 			else if ( new Regex( @"^\s*SETLOCAL\s+((EN|DIS)ABLEEXTENSIONS\s+)?ENABLEDELAYEDEXPANSION(\b|$)", ignorecase ).IsMatch( line ) )
  2492. 			{
  2493. 				delayedvarexp = true;
  2494. 				delayedvarglobal = true;
  2495. 				Console.ResetColor( );
  2496. 				Console.WriteLine( line );
  2497. 				WriteSpanHTML( line );
  2498. 			}
  2499. 			// check if delayed variable expansion is disabled (will choke on nested SETLOCALs)
  2500. 			else if ( new Regex( @"^\s*ENDLOCAL\b", ignorecase ).IsMatch( line ) )
  2501. 			{
  2502. 				delayedvarexp = false;
  2503. 				Console.ResetColor( );
  2504. 				Console.WriteLine( line );
  2505. 				WriteSpanHTML( line );
  2506. 			}
  2507. 			else
  2508. 			{
  2509. 				// ParseLinePerCharacter( line );
  2510. 				ParseLinePerCharacter2( line );
  2511. 			}
  2512. 		}
  2513.  
  2514.  
  2515. 		public static void ParseLinePerCharacter( string line )
  2516. 		{
  2517. 			#region Initialize Inline Variables
  2518.  
  2519. 			string part = string.Empty;
  2520. 			string buildword = string.Empty;
  2521. 			string lastword = string.Empty;
  2522. 			int doublequotes = 0;
  2523. 			int singlequotes = 0;
  2524. 			bool doublepercent = false;
  2525. 			bool doublequoted = false;
  2526. 			bool echoed = false;
  2527. 			bool escnext = false;
  2528. 			bool forfloop = false;
  2529. 			bool inexcl = false;
  2530. 			bool inperc = false;
  2531. 			bool isparenthesis = false;
  2532. 			bool redirection = false;
  2533. 			bool singlequoted = false;
  2534. 			bool tempdblquoted = false;
  2535. 			bool tempinexcl = false;
  2536. 			bool tempinperc = false;
  2537. 			bool tempsngquoted = false;
  2538. 			bool tildestr = false;
  2539.  
  2540. 			#endregion Initialize Inline Variables
  2541.  
  2542. 			// check if inside a FOR /F loop
  2543. 			forfloop = new Regex( @"(^\s*|\b|\(|\&)FOR\s+/F\s", ignorecase ).IsMatch( line );
  2544.  
  2545. 			char prevchr = ' ';
  2546. 			int index = -1;
  2547.  
  2548. 			foreach ( char chr in line )
  2549. 			{
  2550. 				index += 1;
  2551.  
  2552. 				#region Character Type
  2553.  
  2554. 				switch ( chr )
  2555. 				{
  2556. 					case '"':
  2557. 						doublequoted = ( doublequoted ^ !escnext ) && !echoed;
  2558. 						if ( !escnext && !echoed )
  2559. 						{
  2560. 							doublequotes += 1;
  2561. 						}
  2562. 						doublepercent = false;
  2563. 						escnext = false;
  2564. 						inexcl = false;
  2565. 						inperc = false;
  2566. 						redirection = false;
  2567. 						tildestr = false;
  2568. 						break;
  2569. 					case (char) 39:
  2570. 						singlequoted = ( singlequoted ^ !escnext ) && !echoed;
  2571. 						if ( !escnext && !echoed && !doublequoted )
  2572. 						{
  2573. 							singlequotes += 1;
  2574. 						}
  2575. 						doublepercent = false;
  2576. 						escnext = false;
  2577. 						inexcl = false;
  2578. 						inperc = false;
  2579. 						redirection = false;
  2580. 						tildestr = false;
  2581. 						break;
  2582. 					case '^':
  2583. 						doublepercent = false;
  2584. 						escnext = ( prevchr != chr );
  2585. 						inexcl = false;
  2586. 						inperc = false;
  2587. 						tildestr = false;
  2588. 						break;
  2589. 					case '%':
  2590. 						if ( prevchr == '%' )
  2591. 						{
  2592. 							doublepercent = true;
  2593. 						}
  2594. 						else
  2595. 						{
  2596. 							doublepercent = false;
  2597. 							inperc = !inperc;
  2598. 						}
  2599. 						escnext = false;
  2600. 						inexcl = false;
  2601. 						redirection = false;
  2602. 						tildestr = false;
  2603. 						break;
  2604. 					case '!':
  2605. 						if ( delayedvarexp )
  2606. 						{
  2607. 							inexcl = !inexcl;
  2608. 						}
  2609. 						doublepercent = false;
  2610. 						escnext = false;
  2611. 						inperc = false;
  2612. 						redirection = false;
  2613. 						tildestr = false;
  2614. 						break;
  2615. 					case '~':
  2616. 						if ( prevchr == '%' )
  2617. 						{
  2618. 							inperc = false;
  2619. 							tildestr = true;
  2620. 						}
  2621. 						else if ( prevchr == '!' )
  2622. 						{
  2623. 							inexcl = false;
  2624. 							tildestr = true;
  2625. 						}
  2626. 						escnext = false;
  2627. 						redirection = false;
  2628. 						break;
  2629. 					case '&':
  2630. 						if ( prevchr == chr )
  2631. 						{
  2632. 							doublequoted = false;
  2633. 							echoed = false;
  2634. 							inperc = false;
  2635. 							redirection = false;
  2636. 						}
  2637. 						else
  2638. 						{
  2639. 							redirection = !escnext;
  2640. 						}
  2641. 						if ( singlequoted && redirection )
  2642. 						{
  2643. 							message = "Unescaped ampersand(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. 						inexcl = false;
  2654. 						inperc = false;
  2655. 						tildestr = false;
  2656. 						break;
  2657. 					case '|':
  2658. 						if ( prevchr == chr )
  2659. 						{
  2660. 							doublequoted = false;
  2661. 							echoed = false;
  2662. 							inperc = false;
  2663. 							redirection = false;
  2664. 						}
  2665. 						else
  2666. 						{
  2667. 							redirection = !escnext;
  2668. 						}
  2669. 						if ( singlequoted && redirection )
  2670. 						{
  2671. 							message = "Unescaped pipe symbol(s) in singlequoted text (FOR /F loop" + ( forfloop ? string.Empty : "?" ) + ")";
  2672. 							if ( errormsg.IndexOf( message ) > -1 )
  2673. 							{
  2674. 								allerrors += string.Format( "{0}Line {1,5:0}:\t{2}", newline, linenum, message );
  2675. 								errormsg += string.Format( "{0}{1}.", newline, message );
  2676. 							}
  2677. 							errinline = true;
  2678. 						}
  2679. 						doublepercent = false;
  2680. 						escnext = false;
  2681. 						echoed = false;
  2682. 						incomment = false;
  2683. 						inexcl = false;
  2684. 						inperc = false;
  2685. 						tildestr = false;
  2686. 						break;
  2687. 					case '>':
  2688. 						redirection = !escnext;
  2689. 						if ( singlequoted && redirection )
  2690. 						{
  2691. 							message = "Unescaped \"greater than\" symbol(s) in singlequoted text (FOR /F loop" + ( forfloop ? string.Empty : "?" ) + ")";
  2692. 							if ( errormsg.IndexOf( message ) > -1 )
  2693. 							{
  2694. 								allerrors += string.Format( "{0}Line {1,5:0}:\t{2}", newline, linenum, message );
  2695. 								errormsg += string.Format( "{0}{1}.", newline, message );
  2696. 							}
  2697. 							errinline = true;
  2698. 						}
  2699. 						doublepercent = false;
  2700. 						escnext = false;
  2701. 						echoed = false;
  2702. 						incomment = false;
  2703. 						inexcl = false;
  2704. 						inperc = false;
  2705. 						tildestr = false;
  2706. 						break;
  2707. 					case '<':
  2708. 						redirection = !escnext;
  2709. 						if ( singlequoted && redirection )
  2710. 						{
  2711. 							message = "Unescaped \"less than\" symbol(s) in singlequoted text (FOR /F loop" + ( forfloop ? string.Empty : "?" ) + ")";
  2712. 							if ( errormsg.IndexOf( message ) > -1 )
  2713. 							{
  2714. 								allerrors += string.Format( "{0}Line {1,5:0}:\t{2}", newline, linenum, message );
  2715. 								errormsg += string.Format( "{0}{1}.", newline, message );
  2716. 							}
  2717. 							errinline = true;
  2718. 						}
  2719. 						doublepercent = false;
  2720. 						escnext = false;
  2721. 						inexcl = false;
  2722. 						inperc = false;
  2723. 						tildestr = false;
  2724. 						break;
  2725. 					case ' ':
  2726. 					case '\t':
  2727. 						if ( inexcl )
  2728. 						{
  2729. 							unterminatedexclamations += 1;
  2730. 							message = "Unterminated exclamation marks or whitespace in (delayed) variable name";
  2731. 							if ( errormsg.IndexOf( message ) > -1 )
  2732. 							{
  2733. 								allerrors += string.Format( "{0}Line {1,5:0}:\t{2}", newline, linenum, message );
  2734. 								errormsg += string.Format( "{0}{1}.", newline, message );
  2735. 							}
  2736. 							errinline = true;
  2737. 						}
  2738. 						if ( inperc )
  2739. 						{
  2740. 							unterminatedexclamations += 1;
  2741. 							message = "Unterminated percent signs or whitespace in variable name";
  2742. 							if ( errormsg.IndexOf( message ) > -1 )
  2743. 							{
  2744. 								allerrors += string.Format( "{0}Line {1,5:0}:\t{2}", newline, linenum, message );
  2745. 								errormsg += string.Format( "{0}{1}.", newline, message );
  2746. 							}
  2747. 							errinline = true;
  2748. 						}
  2749. 						doublepercent = false;
  2750. 						escnext = false;
  2751. 						inexcl = false;
  2752. 						inperc = false;
  2753. 						redirection = false;
  2754. 						tildestr = false;
  2755. 						break;
  2756. 					case '(':
  2757. 						if ( !echoed && !escnext )
  2758. 						{
  2759. 							parenthesis += 1;
  2760. 						}
  2761. 						doublepercent = false;
  2762. 						//inexcl = false;
  2763. 						//inperc = false;
  2764. 						isparenthesis = true;
  2765. 						redirection = false;
  2766. 						tildestr = false;
  2767. 						break;
  2768. 					case ')':
  2769. 						if ( !echoed && !escnext )
  2770. 						{
  2771. 							parenthesis -= 1;
  2772. 						}
  2773. 						if ( parenthesis < 0 )
  2774. 						{
  2775. 							message = "Too many closing parenthesis";
  2776. 							if ( allerrors.IndexOf( message ) == -1 )
  2777. 							{
  2778. 								allerrors += string.Format( "{0}Line {1,5:0}:\t{2}", newline, linenum, message );
  2779. 								errormsg += string.Format( "{0}{1}.", newline, message );
  2780. 								errinline = true;
  2781. 							}
  2782. 						}
  2783. 						doublepercent = false;
  2784. 						//inexcl = false;
  2785. 						//inperc = false;
  2786. 						isparenthesis = true;
  2787. 						redirection = false;
  2788. 						tildestr = false;
  2789. 						break;
  2790. 					case 'a':
  2791. 					case 'd':
  2792. 					case 'f':
  2793. 					case 'n':
  2794. 					case 'p':
  2795. 					case 's':
  2796. 					case 't':
  2797. 					case 'x':
  2798. 					case 'z':
  2799. 						// "$PATH:" is ignored in the highlighting
  2800. 						if ( tildestr )
  2801. 						{
  2802. 							inexcl = false;
  2803. 							inperc = false;
  2804. 							tildestr = true;
  2805. 						}
  2806. 						escnext = false;
  2807. 						redirection = false;
  2808. 						break;
  2809. 					case '0':
  2810. 					case '1':
  2811. 					case '2':
  2812. 					case '3':
  2813. 					case '4':
  2814. 					case '5':
  2815. 					case '6':
  2816. 					case '7':
  2817. 					case '8':
  2818. 					case '9':
  2819. 						if ( ( prevchr == '%' ) || tildestr )
  2820. 						{
  2821. 							inexcl = false;
  2822. 							inperc = false;
  2823. 							tildestr = true;
  2824. 						}
  2825. 						escnext = false;
  2826. 						redirection = false;
  2827. 						break;
  2828. 					default:
  2829. 						doublepercent = false;
  2830. 						escnext = false;
  2831. 						redirection = false;
  2832. 						tildestr = false;
  2833. 						if ( tildestr )
  2834. 						{
  2835. 							if ( "ABCDEFGHIJKLMNOPQRSTUVWXYZ?".IndexOf( chr.ToString( ), StringComparison.InvariantCultureIgnoreCase ) > -1 )
  2836. 							{
  2837. 								if ( "adfnpstxz".IndexOf( prevchr.ToString( ) ) > -1 )
  2838. 								{
  2839. 									if ( line.IndexOf( "%%" + chr.ToString( ) ) > 3 && line.IndexOf( "%%" + chr.ToString( ) ) < index )
  2840. 									{
  2841. 										tildestr = true;
  2842. 									}
  2843. 								}
  2844. 							}
  2845. 						}
  2846. 						break;
  2847. 				}
  2848.  
  2849. 				#endregion Character Type
  2850.  
  2851. 				// check for unescaped redirection symbols in FOR /F loops
  2852. 				if ( redirection && singlequoted && ( parenthesis % 2 == 1 ) )
  2853. 				{
  2854. 					message = "Unescaped redirection symbol(s) in single quoted text (FOR /F loop?)";
  2855. 					if ( errormsg.IndexOf( message ) > -1 )
  2856. 					{
  2857. 						allerrors += string.Format( "{0}Line {1,5:0}:\t{2}", newline, linenum, message );
  2858. 						errormsg += string.Format( "{0}{1}.", newline, message );
  2859. 					}
  2860. 					errinline = true;
  2861. 				}
  2862.  
  2863. 				#region ECHOed text and comments
  2864.  
  2865. 				// check if we are dealing with ECHOed text or comments
  2866. 				if ( chr == ' ' || chr == '\t' )
  2867. 				{
  2868. 					lastword = buildword.ToUpper( ).Trim( );
  2869. 					buildword = string.Empty;
  2870. 				}
  2871. 				else
  2872. 				{
  2873. 					buildword += chr.ToString( );
  2874. 				}
  2875. 				if ( lastword == "ECHO" || lastword == "@ECHO" )
  2876. 				{
  2877. 					echoed = true;
  2878. 				}
  2879. 				else if ( ( lastword == "REM" || lastword == "::" ) && !echoed )
  2880. 				{
  2881. 					incomment = true;
  2882. 					commentlines += 1;
  2883. 				}
  2884.  
  2885. 				#endregion ECHOed text and comments
  2886.  
  2887. 				// as soon as an unescaped redirection symbol is encountered, we no longer have ECHOed text
  2888. 				if ( redirection && !doublequoted && !escnext )
  2889. 				{
  2890. 					echoed = false;
  2891. 				}
  2892.  
  2893. 				#region Highlighting
  2894.  
  2895. 				// correctly highlight the character we just parsed
  2896. 				if ( redirection )
  2897. 				{
  2898. 					// redirection symbols and parenthesis are highlighted in red
  2899. 					Console.ForegroundColor = redirectionfgcolor;
  2900. 				}
  2901. 				else if ( inexcl || inperc || tildestr || doublepercent || tempinexcl || tempinperc )
  2902. 				{
  2903. 					// variables and parameters are highlighted in green
  2904. 					Console.ForegroundColor = variablesfgcolor;
  2905. 				}
  2906. 				else if ( isparenthesis && !echoed )
  2907. 				{
  2908. 					// redirection symbols and parenthesis are highlighted in red
  2909. 					Console.ForegroundColor = parenthesisfgcolor;
  2910. 				}
  2911. 				else if ( echoed )
  2912. 				{
  2913. 					// ECHOed text is highlighted in cyan, with the exception of variables
  2914. 					Console.ForegroundColor =