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